← Back to Writeups
CUSTOMMediumWeb

level19

XESXOR9/2/20262 min read
#web#custom#medium#anticheat_timing_bypass#line_by_line_request#raw_socket_http#url_encoding_special_chars

level19

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: anticheat_timing_bypass, line_by_line_request, raw_socket_http, url_encoding_special_chars

Summary

Walkthrough of the CUSTOM Web level19. Task: Web challenge with anticheat proxy blocking automated tools. Solution: Send POST request with URL-encoded ASCII art via raw sockets with 1-second delays between lines to bypass timing-based bot

Description

Web challenge with an anticheat proxy requiring a POST request with URL-encoded ASCII art via raw sockets with delays between lines.

Analysis

Initial Reconnaissance

  • URL http://kslweb1.spb.ctf.su/second/level19 returns 403 Forbidden with redirect to port 58080
  • Server: nginx/1.18.0 (Ubuntu) with PHP/5.5.9-1ubuntu4.21

Anticheat Proxy

  • Port 58080 contains an anticheat proxy that detects automated tools (curl, requests)
  • The proxy checks whether HTTP request lines are sent all at once (tool) or one by one (human)
  • Solution: use raw sockets with time.sleep(1) between each line ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Task

GET request returns instructions:

Nope, you should send one POST field 'smiley' with this exact face - it contains 6 lines of text:

+|||||+
| - - |
| O_o |
|  |  |
| \__ |
 \___/

(how post values are encoded? if google doesnt help maybe we can see how it is transmitted on some other website?)

Solution

URL-encoding ASCII Art

The 6-line ASCII smiley needs to be encoded for application/x-www-form-urlencoded:

  • Newlines \n → %0A
  • Pipes | → %7C
  • Plus + → %2B
  • Backslash \ → %5C
  • Forward slash / → %2F
  • Spaces → + ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Sending via Raw Sockets

python
#!/usr/bin/env python3
import socket
import time
import urllib.parse

smiley = '+|||||+\n| - - |\n| O_o |\n|  |  |\n| \\__ |\n \\___/'
encoded = urllib.parse.urlencode({'smiley': smiley})

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('kslweb1.spb.ctf.su', 58080))
s.settimeout(10)

lines = [
    'POST /second/level19/ HTTP/1.1\r\n',
    'Host: kslweb1.spb.ctf.su\r\n',
    'Content-Type: application/x-www-form-urlencoded\r\n',
    f'Content-Length: {len(encoded)}\r\n',
    'Connection: close\r\n',
    '\r\n',
    encoded
]
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

for line in lines:
    s.send(line.encode())
    time.sleep(1)

response = b''
while True:
    try:
        chunk = s.recv(4096)
        if not chunk:
            break
        response += chunk
    except:
        break

s.close()
print(response.decode())

Response: Done! Flag: KSL{REDACTED} ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering anticheat_timing_bypass, line_by_line_request, raw_socket_http, url_encoding_special_chars was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-03-08 • CUSTOM • level19 • Difficulty: Medium