Level 23 - Voting Service
Level 23 - Voting Service
Platform: kslweb1 | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-03-08 | Status: Solved Techniques: cookie_chaining, sequential_request_automation, signature_analysis
Summary
Task: Voting service requiring 1337 votes, with cryptographically signed cookies (N:HASH format). Solution: Automated sequential voting through cookie chaining since signature cannot be forged without the secret key.
Level 23 - Voting Service - kslweb1
Description
The task is a voting web service available at http://kslweb1.spb.ctf.su/second/level23/
The server runs on nginx/1.18.0 (Ubuntu), proxying Apache/2.4.7 (Ubuntu) with PHP/5.5.9.
On the first request (without cookie) the page returns:
=== Voting service ===
Currently you have 0 votes.
To vote +1, set this cookie: Set-Cookie: votes=1:7bc858c0d521f3bdf696f7187c2da177
To get flag you need 1337 votes here.
Analysis
Cookie format
The cookie has the format votes=N:HASH, where:
N— current vote countHASH— 32-character hex string (length corresponds to MD5)
Signature bypass attempts
When attempting to forge a cookie (e.g., votes=1337:md5("1337")) the server returns:
'votes' cookie has an invalid crypto signature. It's ok, you can't cheat this.
The following hash cracking attempts were made:
- Simple MD5 of the number:
md5("1337")— doesn't work - MD5 with common salts (prefix/suffix)
- Brute-force of salts up to 4 characters
- Dictionary attacks with popular words
- HMAC-MD5 variations
- Double-MD5
- Truncated SHA1
Result: All attempts failed. The hash is generated server-side using a secret key that cannot be guessed.
How it works
- When sending a valid cookie
votes=N:HASHthe server verifies the signature - If the signature is valid, the server returns the next cookie
votes=N+1:NEW_HASH - The only way to get a valid cookie for 1337 votes is to go through all 1336 intermediate steps
Solution
Since the cryptographic signature cannot be forged without knowing the secret key, the sequential voting process must be automated.
Algorithm
- Start with cookie
votes=1:7bc858c0d521f3bdf696f7187c2da177 - Send a GET request with the current cookie
- Parse the response and extract the new cookie from the
Set-Cookie: votes=...line - Repeat 1336 times
- On the 1337th step, get the flag
Solve script
python read-only • copy allowed#!/usr/bin/env python3 """ kslweb1 Level 23 - Voting Service Solver Automating sequential voting through cookie chaining """ import asyncio import aiohttp import re async def main(): url = 'http://kslweb1.spb.ctf.su/second/level23/' cookie_val = '1:7bc858c0d521f3bdf696f7187c2da177' # Use connection pooling for speedup connector = aiohttp.TCPConnector(limit=1, force_close=False) async with aiohttp.ClientSession(connector=connector) as session: for i in range(1, 1337): async with session.get(url, cookies={'votes': cookie_val}) as resp: body = await resp.text() # Check if we got the flag if 'flag' in body.lower() and 'need' not in body.lower(): print(f'[+] FLAG FOUND at step {i}!') print(body) return # Extract next cookie for line in body.split('\n'): if 'Set-Cookie:' in line: match = re.search(r'Set-Cookie:\s*votes=(\S+)', line) if match: cookie_val = match.group(1) break # Progress if i % 100 == 0: print(f'[*] Step {i}/1336: votes={cookie_val[:20]}...') # Final request with 1337 votes async with session.get(url, cookies={'votes': cookie_val}) as resp: body = await resp.text() print(f'[+] Final response:\n{body}') if __name__ == '__main__': asyncio.run(main())