
CVE-2020-13277 標的環境: Gitlab 論理脆弱性 - 任意のユーザーが権限を超えてプライベートリポジトリにアクセス可能
CVE-2020-13277 学習環境: Gitlab 論理脆弱性 - 任意ユーザーによるプライベートリポジトリへの権限昇格アクセス
CVE-2020-13277
├── README.md ............... [この README 説明]
├── imgs .................... [README 説明用画像]
├── gitlab .................. [Gitlab コンテナのマウントディレクトリ]
│ ├── Dockerfile .......... [Gitlab の Docker ビルドファイル]
│ ├── config .............. [Gitlab 設定マウントディレクトリ]
│ ├── data ................ [Gitlab データマウントディレクトリ]
│ ├── logs ................ [Gitlab ログマウントディレクトリ]
│ ├── keys ................ [Gitlab ライセンス解除キー格納ディレクトリ]
│ └── runner .............. [Runner コンテナのマウントディレクトリ]
├── license ................. [ライセンス解除用コンテナビルドディレクトリ]
│ ├── Dockerfile .......... [License の Docker ビルドファイル]
│ └── license.rb .......... [ライセンス解除キー生成 Ruby スクリプト]
├── docker-compose.yml ...... [Docker ビルド設定]
├── keygen.ps1 .............. [Windows: ワンクリックライセンス解除キー生成]
├── keygen.sh ............... [Linux: ワンクリックライセンス解除キー生成]
├── run.ps1 ................. [Windows: ワンクリック Gitlab 学習環境起動]
├── run.sh .................. [Linux: ワンクリック Gitlab 学習環境起動]
├── register.ps1 ............ [Windows: ワンクリック Runner 登録]
├── register.sh ............. [Linux: ワンクリック Runner 登録]
├── stop.ps1 ................ [Windows: ワンクリック Gitlab 学習環境停止]
└── stop.sh ................. [Linux: ワンクリック Gitlab 学習環境停止]
この脆弱性は主に Mirror Repository - リポジトリのミラー同期バックアップ機能を利用します。
Mirror の同期方向には次の2種類があります:
この脆弱性で利用するのは Pull 方向の Mirror Repository です
Gitlab には CE(コミュニティ無料版)と EE(エンタープライズ有料版)の2種類があり、Gitlab 公式も この脆弱性は CE と EE の以下のバージョンに影響すると発表しています:
>=10.6, <12.9.10>=12.10, <12.10.11>=13.0, <13.0.6しかし、これらのバージョンの Gitlab Docker Image がすべて学習環境の構築に使用できるわけではありません。その理由は:
言い換えると、Docker で学習環境を構築するには、Gitlab-EE 版を選択し、それをクラック(お金に余裕があればライセンスを購入しても構いません)して Mirror Repository - Pull 機能を有効化する必要があります。
しかし Gitlab-EE をクラックしても、10.x、12.x、13.x のいずれであっても、Mirror Repository - Pull の URL にローカルパスが含まれていると、Import url is blocked: Requests to localhost are not allowed というエラーが発生します。
./keygen.sh または ./keygen.ps1./run.sh または ./run.ps1鍵ペア生成時に公開鍵はすでに Gitlab コンテナのバックグラウンドに書き込まれていますが、秘密鍵をフロントエンドから Gitlab にアップロードしてクラックを完了する必要があります:
./gitlab/keys/ ディレクトリに生成されます。その中の .gitlab-license の内容(秘密鍵)をコピーしますEnter license key を選択し、秘密鍵を貼り付け、Upload license ボタンをクリックするとクラック完了ですこれで Mirror Repository - Pull 機能が有効になりました

Outbound requests を見つけ、Allow requests to the local network from hooks and services にチェックを入れて保存しますこれで Mirror Repository - Pull がローカル Repository のプルをサポートするようになりました

./register.sh $TOKEN または ./register.ps1 $TOKENこれで全ての Repository がこの Runner を使用して CI スクリプト(Pipeline Jobs)を実行できるようになりました

検証手順は公式の Issue を参考にできますが、以下の検証手順ではこの学習環境に合わせて一部の手順を微調整しています
root ユーザーで http://127.0.0.1/admin/users ページを開き、3つのアカウントを作成します:
victim: 被害者アカウントattacker1: 攻撃者アカウント1attacker2: 攻撃者アカウント2アカウント作成時に初期パスワードは設定できません。Gitlab はデフォルトで設定された Email に初期パスワードを送信します。便宜上、Email は適当に入力し、まずアカウントを作成し、その後すぐにそのアカウントを編集して、root でそのアカウントの初期パスワードを設定できます(Email 経由は不要)

victim アカウントで Gitlab にログインしますNew Project を作成:
targetPrivateREADME.md ファイルを作成し、内容を mykey is abcxyz など適当に設定します明らかに
targetはvictimのプライベートリポジトリであり、我々の目標は脆弱性を利用してこのリポジトリの内容を取得することです

attacker1 アカウントで Gitlab にログインしますNew Project を作成:
pocPublic.gitlab-ci.yml ファイルを作成し、内容は以下の通り:image: "ruby:2.6"
rspec:
script:
- git clone http://gitlab-ci-token:[email protected]/victim/target.git
- cd target
- ls -lah .
- cat README.md
【目的】 以降の操作で、victim が知らないうちに、自分の権限でこの CI スクリプトを実行させるため。
現在の学習環境では証明書が設定されていないため、
httpプロトコルを使用する必要があります。また172.168.30.2はdocker-compose.ymlで Gitlab コンテナに割り当てられた IP です。この CI スクリプトは最終的に Runner 経由で実行されるため、現在の学習環境では Runner と Gitlab は同じコンテナではないため、Docker が割り当てた IP アドレスを使用する必要があります

attacker2 アカウントで Gitlab にログインしますNew Group を作成:
testPublictest グループ内に完全に空のリポジトリ New Project を作成:
pocPublic
ミラーリポジトリを設定 Settings => Repository => Pull from a remote repository:
Mirror repository: チェックGit repository URL: http://GITLAB/attacker1/poc と入力Password: (空欄のまま。Pull 元のリポジトリは Public なのでパスワード不要)Trigger pipelines for mirror updates: チェック(ミラー同期時に CI スクリプトを実行)設定が成功すると、30分ごとにソースリポジトリに変更がないかチェックし、変更があれば強制同期されます。
Git repository URLは先ほど作成した poc リポジトリを指しています。127.0.0.1 を使用しないのは、Pull がローカルリポジトリのプルを禁止しているためですが、DNS バイパスが可能です:GITLABはdocker-compose.ymlで Gitlab コンテナに割り当てられた hostname で、デフォルトで/etc/hostsに設定されます。ホストマシンではGITLABを解決できませんが、コンテナ内ではhttp://127.0.0.1/attacker1/pocにアクセスすることと同等です

attacker2 アカウントを使用test グループのユーザー管理victim ユーザーをグループに追加:
Add new member to test: victimPermissions: OwnerExpiration date: (空欄、無期限)=> Setting => Account => Delete account で現在のアカウント(attacker2)を削除【目的】 test グループ内には victim と attacker2 の2人の Owner しかいません。attacker2 ユーザーが削除されると、test グループとその下のすべてのリポジトリの所有権は強制的に victim に移ります。現在 test グループには attacker1/poc からミラー同期された test/poc リポジトリがあるため、test/poc リポジトリが強制的に victim ユーザーに「養子縁組」されたことになります。


まず、現在の状況を整理します:
victim のために test/poc リポジトリを作成したtest/poc リポジトリの内容は attacker1/poc リポジトリからミラー同期されている(30分ごとに変更をチェックし、必要に応じて同期)attacker1/poc リポジトリの内容は攻撃者が制御しており、CI スクリプト .gitlab-ci.yml も含むtest/poc リポジトリに Trigger pipelines for mirror updates が設定されているため、同期のたびに CI スクリプトが実行されるtest/poc リポジトリの Owner は victim であるため、CI スクリプトは victim の権限で実行される
先ほど設定した CI スクリプト .gitlab-ci.yml の内容を振り返ると、この poc は Runner 内で victim の権限を使用して、その Private リポジトリ target のディレクトリ構造と README.md にアクセスしています:
image: "ruby:2.6"
rspec:
script:
- git clone http://gitlab-ci-token:[email protected]/victim/target.git
- cd target
- ls -lah .
- cat README.md
その後、攻撃者は attacker1/poc リポジトリ内の重要でない内容を適宜変更するだけで、最悪の場合30分待てば、victim の権限で設定した CI スクリプトを実行させることができます。
攻撃者は Pipeline Jobs の実行結果に直接アクセスする権限はありませんが、スクリプトの出力先を調整する(例えば、リポジトリの内容を指定の Email や FTP サーバーに送信する)ことで、リポジトリコードを盗むことができます。

Admin area => Settings => Network => Outbound requests で Allow requests to the local network from hooks and services を設定するとローカル URL を構成できるようになりますが、ミラー同期時に 2:Fetching remote upstream failed: fatal: unable to access http://127.0.0.1/xxxx/: The requested URL returned error: 301 というエラーが発生します。つまり、Pull Remote Repository のみが利用可能です。
しかし幸いなことに、12.x と 13.x ではローカル URL の判定が非常に厳格ですが、10.x バージョンには回避策があります:Pull URL を構成するときに、ローカルで設定した DNS サービス名を指定することでバイパスできます。
以上から、最終的に gitlab-ee:10.6.0-ee.0 バージョンの Docker Image を使用してこの学習環境を構築する必要があります。
前述の説明からもわかるように、この脆弱性の利用条件はかなり厳しく、基本的に貧乏な人がこの脆弱性の影響を受けることはほとんどありません