Open2
CVE勉強ノート
CVE-2025-8264
概要
-
対象ソフトウェア : z-push/z-push-dev
- ActiveSync互換デバイスを同期するためのオープンソースアプリケーション
- ActiveSync : MicrosoftのPCがモバイルと同期するためのプロトコル(Exchangeのメールサーバーでの使用が有名)
- ActiveSync互換デバイスを同期するためのオープンソースアプリケーション
-
脆弱性 : SQLインジェクション
-
対象のソフトウェアの中でIMAPバックエンドを利用し、IMAP_FROM_SQL_QUERYを設定として使っている部分で発生
脆弱なポイント
$sql = str_replace('#username', $username, str_replace('#domain', $domain, IMAP_FROM_SQL_QUERY));
そもそもこのコードでは何をしたかったの?
- 検索用のSQLのクエリを組み立てたい
IMAP_FROM_SQL_QUERYは、以下のようになっていて、#username
と#domain
をユーザー入力の$username
と$domain
に置き換えて、メールアドレスを作って、usersテーブルからユーザーを検索するためのクエリを作りたかった。
define('IMAP_FROM_SQL_QUERY', "select first_name, last_name, mail_address from users where mail_address = '#username@#domain'");
なんで脆弱?
- パラメーター化されておらず、SQL構造と値の分離が行われていなかったため、SQLインジェクションに対して脆弱
修正後
$sql = str_replace("'#username'", ":username", str_replace("'#domain'", ":domain", str_replace("'#username@#domain'", ":usernameatdomain", IMAP_FROM_SQL_QUERY)));
$usernameatdomain = $username . '@' . $domain;
if(mb_strpos($sql, ':username') !== false) $sth->bindValue(':username', $username, PDO::PARAM_STR);
if(mb_strpos($sql, ':domain') !== false) $sth->bindValue(':domain', $domain, PDO::PARAM_STR);
if(mb_strpos($sql, ':usernameatdomain') !== false) $sth->bindValue(':usernameatdomain', $usernameatdomain, PDO::PARAM_STR);
何をどういう風に修正した?
- PDOのプレースホルダ(:変数名)を使用することで、パラメーター化が行える
なんで安全?
-
'#username'
等を:username
などの PDO プレースホルダに変換し、PDOの関数を適切に使用することによって「値」として渡すように変更- SQL の構造(SELECT/WHERE 等)と値が完全に分離され、値に含まれる
' OR 1=1 --
のような文字列は ただの値として処理され、SQL構造を変えることはできないため
- SQL の構造(SELECT/WHERE 等)と値が完全に分離され、値に含まれる
早速昨日サボったので、今日は二つやる
CVE-2025-57756
概要
- 対象ソフトウェア : contao/core-bundle
- 保守が容易でプロフェッショナルなウェブサイトを求める人のためのオープンソースの PHP 製CMS
- 脆弱性 : フラグメントのレンダリング処理における認可不備
- 発生ポイント : 検索機能
脆弱なポイント
モジュールの描画部
// Disable indexing if protected
if ($objModule->protected && !preg_match('/^\s*<!-- indexer::stop/', $strBuffer)) {
$groups = StringUtil::deserialize($objModule->groups, true);
if (\count($groups) !== 1 || !\in_array(-1, array_map(\intval(...), $groups), true)) {
<SNIP>
}
}
// コンテンツの描画部
// Disable indexing if protected
if ($objElement->protected && !preg_match('/^\s*<!-- indexer::stop/', $strBuffer)) {
$groups = StringUtil::deserialize($objElement->groups, true);
if (\count($groups) !== 1 || !\in_array(-1, array_map(\intval(...), $groups), true)) {
<SNIP>
}
}
そもそもこのコードでは何をしたかったの?
- 保護されたモジュール/要素なら、検索のインデックス対象から外す(=
indexer::stop
を付ける)処理
なんで脆弱?
- フラグメントとして描画するとき、実際に表示しているレコードを表す変数ではなく、
$objModule
/$objElement
を見ていたため、ケースによって保護判定がずれ、保護済みコンテンツがインデックスされてしまうことがあった- フラグメントとして描画
- 断片ごとに描いて組み立てる方式
- フラグメントとして描画
修正後
// Disable indexing if protected
if ($objRow->protected && !preg_match('/^\s*<!-- indexer::stop/', $strBuffer)) {
$groups = StringUtil::deserialize($objRow->groups, true);
if (\count($groups) !== 1 || !\in_array(-1, array_map(\intval(...), $groups), true)) {
<SNIP>
}
}
何をどういう風に修正した?
-
判定対象を
$objModule
か$objElement
から$objRow
に統一- つまり、実際にレンダリングしている1件に対して常に保護判定を行うようにした
なんで安全?
-
参照の齟齬がなくなる
- 保護付きフラグメントも例外なくインデックスから除外される
-
既存の除外条件(
indexer::stop
コメントやグループ判定)のロジック自体は維持- 意図した公開/非公開の境界を正しく反映できるようになった
要は、本来は公開しないコンテンツがフラグメントとして検索インデックス化しているとき、公開しないか公開するかの条件分岐がずれることによって、公開したくないページも検索結果に表示されるってこと