CubeCart 6.1.12 - 管理者認証バイパス

Robin Peraglie photo

Robin Peraglie

Vulnerability Researcher

TL;DR 概要

  • Sonarのセキュリティ研究チームは、CubeCartというPHPベースのeコマースプラットフォームにおける認証バイパスの脆弱性を公開し、特定の条件下で攻撃者が管理者認証をバイパスできるようにしました。
  • このバイパスは、CubeCartが管理者のログイン資格情報を検証する際の論理的な欠陥を利用し、認証されていないユーザーがストアのバックエンドに完全な管理者アクセスを得ることを可能にします。
  • 管理者アクセスを持つ攻撃者は、顧客データを流出させたり、ストアの設定を変更したり、悪意のあるコードを注入したり、基盤となるサーバーにピボットしたりすることができます。
  • CubeCartの管理者は、利用可能なセキュリティパッチを直ちに適用し、管理インターフェースに対するIP許可リストなどの追加のアクセス制御を実装する必要があります。

CubeCartはオープンソースのeコマースソリューションです。私たちの最新のセキュリティ分析の一つで、このウェブアプリケーションにおいて、管理者としてログインするために必要な認証メカニズムを回避できる2つの欠陥を発見しました(CVE-2018-20716)。一度バイパスされると、攻撃者はウェブサーバー上で任意のコードを実行し、すべての機密ファイルやデータを盗むことができます。

パスワードを忘れました!

両方の脆弱性は、CubeCartの「パスワードを忘れました!」機能を通じて悪用可能です。これは、ファイルclasses/cubecart.class.phpのメソッド_recovery()に実装されています。ユーザーがパスワードを忘れた場合、この機能を使用してメールアドレス、有効なパスワードリセットトークン、新しいパスワードを入力してリセットを行うことができます。

classes/cubecart.class.php

2761    private function _recovery() {
2762        if (isset($_POST['email']) 
2763        && isset($_POST['validate']) 
2764        && isset($_POST['password'])) {
2765            $GLOBALS['user']->passwordReset($_POST['email'], 
2766                $_POST['validate'], 
2767                $_POST['password']);
2768        }

このメソッドの冒頭で、これら3つのユーザー制御パラメータが、classes/user.class.phpにあるUserクラスのpasswordReset()メソッドに渡されます。このメソッドはアカウントの取得を担当しています。

classes/user.class.php

679    public function passwordReset($email, $verification, $password) {
680        if (filter_var($email, FILTER_VALIDATE_EMAIL) 
681        && !empty($verification) &&!empty($password['password'])
682        && !empty($password['passconf']) 
683        && ($password['password'] === $password['passconf'])) {
684
685            if (($check = $GLOBALS['db']->select('CubeCart_customer', 
686                array('customer_id', 'email'),
687                array('email'=>$email, 'verify'=>$verification)))!==false) {
688                ⋮
689                // Password reset successful
690                ⋮
691            }
692        }	
693        ⋮
694        return false;    // Password reset failed
695    }

passwordReset()メソッドは、メールが有効なメールアドレスであるか、すべてのパラメータが空でないか、パスワードが一致しているかを確認します(行680-683)。これらのチェックのいずれかが失敗すると、パスワードリセットの進行は行694で失敗します。それ以外の場合、次のチェックは行685-687のselect()呼び出しによって発行されるデータベースクエリです。ここで、ユーザーが提供した$emailと$verificationトークンが引数として使用されます。

classes/database.class.php

569    public function select($table, $columns = false, $where = false) {
570        $table_where = $table;
571        ⋮
572        $parent_query = "SELECT $sql_cache $calc_rows ".
573            implode(', ', $cols). " FROM $wrapper{$prefix}$table$wrapper ".
574            $this->where($table_where, $where)." $group $orderString $limit;";
575        ⋮
576        $this->_execute($cache);

select()メソッドはSQLクエリを構築し、それをデータベースに送信します(行576)。SELECTクエリのWHERE句を構築するために、アプリケーションは行574で脆弱なメソッドwhere()を使用します。次の2つのセクションでは、このwhere()メソッドを分析し、個別に検出された2つの脆弱性を紹介します。

認証されていないブラインドSQLインジェクション

database.class.phpのwhere()メソッドは、PHPの組み込み関数mysql_real_escape_string()を使用して、2番目のパラメータ$whereArrayに提供された値を完全にサニタイズします。しかし、値が配列である場合(行811)、配列の各値が行816でサニタイズされずにSQLクエリに連結されます。

classes/database.class.php

807    public function where($table, $whereArray = null, $label = false) {
808    ⋮
809        foreach ($whereArray as $key => $value) {
810            ⋮
811            if (is_array($value)) {
812                foreach ($value as $val) {
813                    ⋮
814                    $or[] = "`$key` IN (".implode(',', $value).')';
815                    ⋮
816                }
817                if (isset($or) && is_array($or)) {
818                    $where[] = implode(' OR ', $or);
819                    unset($or);
820                }
821            }
822            ⋮
823        }
824        return 'WHERE '.implode(' AND ', $where);

攻撃者として、私たちはユーザー入力として配列を渡すことができます。これにより、構築されたSQLクエリにSQL構文を注入し、データベースから機密情報を抽出するためのSQLインジェクション攻撃を行うことができます。悪意のあるPOSTリクエストは次のようになります:

email=contact@ripstech.com
validate[]=0)+OR+sleep(10
password[password]=secretnewpassword
password[passconf]=secretnewpassword
token=15f84b621a9982d65f82d6f12764ecdb

注目すべきは、validate入力パラメータがもはや有効なパスワードリセットトークンを含んでおらず、私たちのSQLペイロードを含んでいることです。構築されたSQLクエリは以下のようになります(注入された部分は最後にあります):

SELECT `customer_id`, `email` FROM `cc6111_CubeCart_customer` WHERE 
cc6111_CubeCart_customer.email = 'contact@ripstech.com' 
AND `verify`  IN (0) OR sleep(10);

認証バイパス

私たちの2番目の脆弱性は、SQLインジェクションの脆弱性からわずか数行離れた場所にあり、実際には管理者としてアクセスするためにSQL構文を注入する必要がないことを示しています。database.class.phpファイルのwhere()メソッドは、検索修飾子も導入しています。

classes/cubecart.class.php

807    public function where($table, $whereArray = null, $label = false) {
808        ⋮
809        foreach ($whereArray as $key => $value) {
810            ⋮
811            if (isset($value) && !ctype_alnum($value) || $value=='NULL' || 
812                is_null($value) || $value=='NOT NULL') {
813                    if(preg_match('#^([<>!~\+\-]=?)(.+)#',$value, $match)){
814                        switch ($match[1]) {
815                            case '~':
816                                // Fuzzy searching
817                                $symbol = 'LIKE';
818                                $value = "%{$match[2]}%";
819                                break;
820                            default:
821                                $symbol = $match[1];
822                                $value = trim($match[2]);
823                        }
824                    }
825                }
826                $full_key = ($label ? $label : $this->_prefix.$table).".".$key;
827                ⋮
828                $where[] = "$full_key $symbol ".$this->sqlSafe($value, true);
829	
830        ⋮
831        return 'WHERE '.implode(' AND ', $where);

基本的にwhere()メソッドは、入力値に特殊文字(< > ~ ! + -)が含まれているかどうかを確認し、最終的にSQLクエリのWHERE句で使用される比較演算子を決定します。たとえば、値にチルダ文字(~)が付いていると、LIKE構文を持つSQLクエリが構築されます(行817-818)。LIKE操作はデータベースでの正確な一致を必要とせず、ワイルドカード文字(%)を許可します。これを利用して、有効なパスワードリセットトークンのチェックをバイパスすることができます。私たちがやるべきことは、パスワードリセットトークンの前に~文字を付け、期待されるトークンの長さだけワイルドカード文字を入れることです。これにより、次のSELECTクエリが生成されます:

select * from CubeCart_customer where email = 'contact@ripstech.com' 
 and verify LIKE '%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%'

私たちが作成した検証トークンを使用すると、正しい検証トークンを必要とするWHERE条件はほとんどの場合で真と評価され、バイパスされます。これにより、攻撃者は数秒で管理者のパスワードをリセットし、管理者としてログインすることができます。管理パネルでは、攻撃者は管理者機能を悪用して任意のPHPコードを実行することができます。

タイムライン

日付

内容

2017/10/11

脆弱性の詳細とPoCをベンダーに提供

2017/10/11

ベンダーがセキュリティ問題を確認

2017/10/16

ベンダーがバージョン6.1.12をリリース

2017/11/23

ベンダーに追加の問題を通知

2017/11/29

ベンダーが修正バージョン6.1.13をリリース

まとめ

私たちは、CubeCartの認証をバイパスし、管理者としてログインできる2つの重大な問題を検出しました。セキュリティ問題は、安全でない方法でSQLクエリをコンパイルするカスタムデータベース抽象化レイヤーに基づいています。準備されたステートメントが存在せず、カスタムSQL連結機能があるため、攻撃者は認証をバイパスするために使用されるSQLクエリを不正に形成することができます。

CubeCartチームには、これらの問題に非常に迅速かつプロフェッショナルに対応していただき、感謝しています。彼らは私たちの報告に即座に対応し、迅速に修正版をリリースしました。CubeCart 6.1.13への更新を直ちに行うことをお勧めします。

関連記事

  • Hibernateインジェクションの悪用
  • Joomla! 3.7.5 - LDAPインジェクションで20秒で乗っ取り
  • BigTree CMS 4.4.6のバックエンドSQLインジェクション
  • dotCMS 5.1.5: H2 SQLインジェクションを利用したRCE
  • Joomla! 3.8.3: SQLインジェクションによる権限昇格
  • OXID eShopsの事前認証乗っ取り
  • SuiteCRM 7.11.4での社内ネットワークへの侵入