1晩で17,000を超えるサイトをハッキングする方法

この話は、Webasystフレームワーク、特にeコマースエンジンShop-Script 7に脆弱性を発見した方法に関するものです。









それはすべて、夕方にロシア語のラップアーティストの商品を購入することにしたという事実から始まりました。 支払い後、注文の詳細へのリンクを含む手紙を受け取りました。

注文情報を表示:

https://o***yshop.com/my/order/21311/9fe684d6508769ef213111ed917d1cce94088/

ピン:3302
(注:注文IDは公開用に変更されています)



ハッシュラインの中央に表示される注文識別子はすぐに私の目を引きました:



9fe684d6508769ef 21311 1ed917d1cce94088



この行がどのように生成されるのか疑問に思っていたため、エンジンのソースコードを確認する必要がありました。 htmlソースページを調べて、ストアで使用されているエンジンを見つけ、小さなGoogleがそれダウンロードする場所を見つけまし



ソースコードを勉強します



この行はランダムに生成され、パターンはトレースされないことが判明しました。 しかし、偶然にも、このハッシュを処理する関数を探していたときに、かなり奇妙なコードのセクションに出会いました。



wa-system / contact / waContact.class.php



関数の保存($データ、$検証)-慎重に、多くのコードを!
/** * Saves contact's data to database. * * @param array $data Associative array of contact property values. * @param bool $validate Flag requiring to validate property values. Defaults to false. * @return int|array Zero, if saved successfully, or array of error messages otherwise */ public function save($data = array(), $validate = false) { $add = array(); foreach ($data as $key => $value) { if (strpos($key, '.')) { $key_parts = explode('.', $key); $f = waContactFields::get($key_parts[0]); if ($f) { $key = $key_parts[0]; if ($key_parts[1] && $f->isExt()) { // add next field $add[$key] = true; if (is_array($value)) { if (!isset($value['value'])) { $value = array('ext' => $key_parts[1], 'value' => $value); } } else { $value = array('ext' => $key_parts[1], 'value' => $value); } } } } else { $f = waContactFields::get($key); } if ($f) { $this->data[$key] = $f->set($this, $value, array(), isset($add[$key]) ? true : false); } else { if ($key == 'password') { $value = self::getPasswordHash($value); } $this->data[$key] = $value; } } $this->data['name'] = $this->get('name'); $this->data['firstname'] = $this->get('firstname'); $this->data['is_company'] = $this->get('is_company'); if ($this->id && isset($this->data['is_user'])) { $c = new waContact($this->id); $is_user = $c['is_user']; $log_model = new waLogModel(); if ($this->data['is_user'] == '-1' && $is_user != '-1') { $log_model->add('access_disable', null, $this->id, wa()->getUser()->getId()); } else if ($this->data['is_user'] != '-1' && $is_user == '-1') { $log_model->add('access_enable', null, $this->id, wa()->getUser()->getId()); } } $save = array(); $errors = array(); $contact_model = new waContactModel(); foreach ($this->data as $field => $value) { if ($field == 'login') { $f = new waContactStringField('login', _ws('Login'), array('unique' => true, 'storage' => 'info')); } else { $f = waContactFields::get($field, $this['is_company'] ? 'company' : 'person'); } if ($f) { if ($f->isMulti() && !is_array($value)) { $value = array($value); } if ($f->isMulti()) { foreach ($value as &$val) { if (is_string($val)) { $val = trim($val); } else if (isset($val['value']) && is_string($val['value'])) { $val['value'] = trim($val['value']); } else if ($f instanceof waContactCompositeField && isset($val['data']) && is_array($val['data'])) { foreach ($val['data'] as &$v) { if (is_string($v)) { $v = trim($v); } } unset($v); } } unset($val); } else { if (is_string($value)) { $value = trim($value); } else if (isset($value['value']) && is_string($value['value'])) { $value['value'] = trim($value['value']); } else if ($f instanceof waContactCompositeField && isset($value['data']) && is_array($value['data'])) { foreach ($value['data'] as &$v) { if (is_string($v)) { $v = trim($v); } } unset($v); } } if ($validate !== 42) { // this deep dark magic is used when merging contacts if ($validate) { if ($e = $f->validate($value, $this->id)) { $errors[$f->getId()] = $e; } } elseif ($f->isUnique()) { // validate unique if ($e = $f->validateUnique($value, $this->id)) { $errors[$f->getId()] = $e; } } } if (!$errors && $f->getStorage()) { $save[$f->getStorage()->getType()][$field] = $f->prepareSave($value, $this); } } elseif ($contact_model->fieldExists($field)) { $save['waContactInfoStorage'][$field] = $value; } else { $save['waContactDataStorage'][$field] = $value; } } // Returns errors if ($errors) { return $errors; } $is_add = false; // Saving to all storages try { if (!$this->id) { $is_add = true; $storage = 'waContactInfoStorage'; if (wa()->getEnv() == 'frontend') { if ($ref = waRequest::cookie('referer')) { $save['waContactDataStorage']['referer'] = $ref; $save['waContactDataStorage']['referer_host'] = parse_url($ref, PHP_URL_HOST); } if ($utm = waRequest::cookie('utm')) { $utm = json_decode($utm, true); if ($utm && is_array($utm)) { foreach ($utm as $k => $v) { $save['waContactDataStorage']['utm_'.$k] = $v; } } } } $this->id = waContactFields::getStorage($storage)->set($this, $save[$storage]); unset($save[$storage]); } foreach ($save as $storage => $storage_data) { waContactFields::getStorage($storage)->set($this, $storage_data); } $this->data = array(); $this->removeCache(); $this->clearDisabledFields(); wa()->event(array('contacts', 'save'), $this); } catch (Exception $e) { // remove created contact if ($is_add && $this->id) { $this->delete(); $this->id = null; } $errors['name'][] = $e->getMessage(); } return $errors ? $errors : 0; }
      
      







$ dataパラメーターには、 「フィールド名」=>「フィールド値」という形式データが含まれています。関数では、 一括割り当てに対する保護に気付きませんでしたが、関数自体が呼び出される前に引数がフィルターされたことを除外しませんでした。 save()が呼び出されるコードのすべての場所を見るのが面倒で、理論を実験的にテストすることにしました。



LANにエンジンをインストールして、最初に決めたのは、「wa_contact」テーブルの構造を調べることでした。



テーブル構造 `wa_contact`




ユーザーが管理パネル(エンジンでは「バックエンド」と呼ばれる)にアクセスできるようにするには、バイヤーにログインとパスワードのフィールドを設定し、is_userフィールドを1にする必要があります。



テスト中



製品をバスケットに追加し、チェックアウトページに移動して、標準フィールドに入力します...新しいフィールドを追加します。







リクエストを送信し、データを使用して管理パネル(/ wa / webasyst /)に移動します。 承認は成功しましたが... ...管理ページは完全に空です:権限がありません。 私は必死にアクセス権の原因となっているテーブルのフィールドを探しますが、そのようなフィールドはなく、すべての権利が適切に別のテーブルに配置されていることを理解しています。







テーブル `wa_contact_rights`にIDごとのユーザーと、マイナス記号付きのIDごとのグループのパーミッションが含まれていることに気付くまで、私は大失敗に近づきました。 すぐに、ユーザーに負のIDを割り当て、グループの権利を取得しました。 言ってすぐに、残りのパラメーターを以前に変更した方法と同様に、顧客[id]を-1だけ変更します。 管理パネルに再度ログインして、管理者グループが利用できるすべての権限を取得します。







最後に何がありますか



このフレームワークの任意のサイト、このエンジンの任意のオンラインストアで完全な管理者権限を取得できる脆弱性。これにより、たとえば、すべての注文と顧客に関する機密情報を受信し、注文のステータスを変更できます(支払い済みなど)ウェブサイトの設定を変更するだけです。



この脆弱性を悪用する条件はありません。サイトでの登録が無効になっている場合にも機能します(実際、注文時に登録が行われます)。



PublicWWWによると、17,000以上のサイトがこのフレームワークを使用しています。



この脆弱性は2か月以上前に報告され、サイトは更新され、誰も傷ついていません。



イベントのタイムライン:



8月8日午後10時30分-Tシャツを購入しました

8月9日、08:00-Webasystの脆弱性を報告、概念実証からのビデオを添付

8月9日13:00-カスタマーサポートから確認を受け取りました

8月14日-報酬を受け取り、脆弱性は解決しました

9月11日-この記事を公開するためのゴーサインを受け取りました



All Articles