Back to Intelligence

Red Heron Exploits Gitea RCE to Breach 13 Organizations Across Six Countries — Detection and Remediation Guide

SA
Security Arsenal Team
September 14, 2026
11 min read

Acronis Threat Research Unit (TRU) has attributed a multi-national intrusion campaign to a suspected Chinese threat actor tracked as Red Heron, which moved with unusual speed to exploit a recently disclosed critical remote code execution vulnerability in Gitea — the widely deployed, self-hosted Git service. Confirmed impact already stands at 13 organizations across six countries, and the reconnaissance footprint suggests the true scope is larger: TRU observed Red Heron scanning 1,386 Gitea instances across seven countries, while maintaining a separate dataset of 477 Taiwan-based systems — a collection pattern consistent with pre-positioned targeting rather than opportunistic spraying.

If you operate an internet-facing Gitea instance, treat this as an active-compromise scenario, not a patching hygiene issue. Self-hosted Git servers hold source code, CI/CD secrets, deploy keys, and developer credentials. A single compromised Gitea host is a beachhead into your entire software supply chain. The defender's priorities, in order: verify exposure, patch or isolate immediately, then hunt backward for signs of compromise — because with exploitation this fast, some of you were breached before the advisory hit your inbox.

Technical Analysis

What is affected

  • Product: Gitea self-hosted Git service (community fork lineage of Gogs), typically deployed on Linux servers, Docker containers, or as a Kubernetes workload.
  • Exposure profile: Any internet-facing instance reachable on its HTTP(S) listener (commonly TCP 3000, or 443/80 behind a reverse proxy). TRU's telemetry confirms scanning at internet scale, so "we're behind a CDN" or "we're not well known" is not a mitigation.
  • Vulnerability class: Critical remote code execution in the Gitea application layer, exploitable against exposed instances. The campaign demonstrated weaponization within a very short window after public disclosure — the hallmark of an actor monitoring vulnerability disclosure pipelines and developing exploits in advance.

Note: the public reporting for this campaign does not yet pin the exploitation to a single published CVE identifier. Rather than guess, defenders should treat any Gitea instance running below the latest stable release as presumptively vulnerable and validate against the official Gitea security advisories at github.com/go-gitea/gitea/security/advisories. Do not wait for CVE-level certainty to act — the actor did not wait.

How the attack works (defender's view)

Based on the campaign tradecraft described by Acronis TRU, the kill chain looks like this:

  1. Internet-scale reconnaissance. Red Heron scanned 1,386 Gitea instances across seven countries, fingerprinting version and exposure. The separate 477-system Taiwan dataset indicates deliberate, curated target selection — likely for intelligence collection against Taiwanese technology and government-adjacent organizations.
  2. Rapid weaponization and initial access. The actor exploited the Gitea RCE to execute arbitrary code in the context of the git service account (the standard Gitea runtime user). Exploitation of a network-reachable web application vulnerability typically requires no authentication beyond what the flaw itself bypasses.
  3. Post-exploitation on the host. Typical observable behavior after compromise of a Git service includes the gitea (or git) process spawning unexpected child processes — shells, downloaders, enumeration tooling — followed by webshell or backdoor placement for persistence, since a patched service loses the original entry vector.
  4. Collection objectives. Git servers are high-value collection points: private repositories, hardcoded credentials in code, CI/CD pipeline variables, deploy SSH keys (/home/git/.ssh), and the Gitea database (user table with password hashes, tokens, and webhooks). Expect credential theft and downstream supply-chain staging, not ransomware-style noise.

Exploitation status

  • Confirmed active, in-the-wild exploitation with 13 victim organizations across six countries.
  • Nation-state attribution: suspected Chinese state-nexus actor (Red Heron), with geographic targeting skew toward Taiwan.
  • Velocity: exploitation began rapidly after disclosure — assume any instance that was unpatched and internet-facing during the exposure window needs compromise assessment, not just patching.

Detection & Response

The most reliable detection surface for this threat is process lineage: the Gitea service process spawning shells, interpreters, or network tools is almost never legitimate. Secondary surfaces are unexpected file writes into web-accessible or persistent locations, and egress connections from the Gitea host to unfamiliar infrastructure.

YAML
---
title: Gitea Process Spawning Shell or Interpreter
description: Detects the Gitea service process spawning shells or script interpreters, consistent with post-exploitation of the Gitea RCE used by Red Heron. Git operations should never invoke interactive shells or interpreters directly.
references:
  - https://thehackernews.com/2026/09/red-heron-exploits-gitea-rce-to.html
  - https://attack.mitre.org/techniques/T1190/
  - https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/09/15
status: experimental
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentCommandLine|contains:
      - 'gitea web'
      - '/app/gitea/gitea'
      - 'gitea serv'
  selection_image:
    CommandLine|contains:
      - '/bin/sh'
      - '/bin/bash'
      - '/bin/dash'
      - 'python'
      - 'perl'
      - 'curl '
      - 'wget '
      - 'base64 -d'
      - 'ncat'
      - 'nc -'
  condition: selection_parent and selection_image
falsepositives:
  - Gitea admin tooling or custom server-side hooks (post-receive, pre-receive) that legitimately invoke scripts — audit hooks under the repositories' hooks directories and whitelist known paths
level: high
---
title: Webshell or Suspicious File Write in Gitea Directories
description: Detects creation of executable or script files in Gitea custom public paths or temporary directories by the service account, consistent with webshell placement after exploitation.
references:
  - https://thehackernews.com/2026/09/red-heron-exploits-gitea-rce-to.html
  - https://attack.mitre.org/techniques/T1505.003/
author: Security Arsenal
date: 2026/09/15
status: experimental
logsource:
  category: file_event
  product: linux
detection:
  selection_path:
    TargetFilename|contains:
      - '/gitea/custom/public/'
      - '/var/lib/gitea/custom/public/'
      - '/app/gitea/custom/public/'
  selection_ext:
    TargetFilename|endswith:
      - '.sh'
      - '.php'
      - '.py'
      - '.jsp'
      - '.war'
  selection_tmp:
    TargetFilename|startswith:
      - '/tmp/'
      - '/dev/shm/'
      - '/var/tmp/'
  condition: selection_path and selection_ext or selection_tmp
falsepositives:
  - Legitimate admin customization of public assets — review file content and writer process before dismissing
level: high
---
title: Gitea Host Initiating Outbound Connection to Rare Destination
description: Detects the Gitea service process establishing outbound network connections, which should be limited to known update, mirror, and webhook destinations. Novel egress from the git service account post-disclosure is a strong compromise indicator.
references:
  - https://thehackernews.com/2026/09/red-heron-exploits-gitea-rce-to.html
  - https://attack.mitre.org/techniques/T1071/
author: Security Arsenal
date: 2026/09/15
status: experimental
logsource:
  category: network_connection
  product: linux
detection:
  selection:
    Image|contains:
      - 'gitea'
    Initiated: 'true'
  filter_known:
    DestinationIp|startswith:
      - '10.'
      - '172.16.'
      - '192.168.'
      - '127.'
  condition: selection and not filter_known
falsepositives:
  - Repository mirroring, webhook delivery, and package proxying — baseline known destinations and alert on first-seen only
level: medium
KQL — Microsoft Sentinel / Defender
// Hunt: Gitea service spawning shells/interpreters — Linux hosts via Syslog/Defender
// Pivot across both Defender for Endpoint telemetry and ingested Syslog for containerized/bare-metal hosts.
let giteaParents = dynamic(["gitea web", "gitea serv", "/app/gitea/gitea"]);
let suspiciousChildren = dynamic(["/bin/sh", "/bin/bash", "/bin/dash", "python", "perl", "curl", "wget", "ncat", "base64"]);
union isfuzzy=true
    (DeviceProcessEvents
     | where InitiatingProcessCommandLine has_any (giteaParents)
     | where ProcessCommandLine has_any (suspiciousChildren)
     | project TimeGenerated=TimeGenerated, Host=DeviceName, Source="MDE",
               Parent=InitiatingProcessCommandLine, Child=ProcessCommandLine,
               Account=AccountName, RemoteIP=""),
    (Syslog
     | where Facility =~ "user" or SyslogMessage has "gitea"
     | where SyslogMessage has_any (suspiciousChildren) and SyslogMessage has "gitea"
     | project TimeGenerated, Host=HostName, Source="Syslog",
               Parent="gitea", Child=SyslogMessage, Account="", RemoteIP=""))
| order by TimeGenerated desc;
// Hunt: Egress from Gitea hosts to first-seen external destinations (7-day baseline vs last 24h)
let lookback = 7d;
let recent = 1d;
let baseline =
    DeviceNetworkEvents
    | where TimeGenerated > ago(lookback) and TimeGenerated < ago(recent)
    | where InitiatingProcessName has "gitea" or InitiatingProcessFolderPath has "gitea"
    | summarize by RemoteIP, RemoteUrl;
DeviceNetworkEvents
| where TimeGenerated > ago(recent)
| where InitiatingProcessName has "gitea" or InitiatingProcessFolderPath has "gitea"
| where RemoteIP !startswith "10." and RemoteIP !startswith "192.168." and RemoteIP !startswith "172.16."
| where RemoteIP !in (baseline | project RemoteIP)
| summarize Connections=count(), Ports=make_set(RemotePort) by DeviceName, RemoteIP, RemoteUrl, InitiatingProcessCommandLine
| order by Connections desc;
VQL — Velociraptor
-- Hunt for post-exploitation indicators on Gitea hosts:
-- 1) gitea/git service account spawning unexpected child processes
-- 2) recently modified executable/script files in Gitea custom public paths and temp dirs
-- 3) current outbound connections from the gitea process

-- Child processes of the gitea service
SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE (CommandLine =~ '(?i)(bash|sh|dash|python|perl|curl|wget|ncat|base64)'
   AND Username =~ '(?i)git')
   OR Name =~ '(?i)^gitea$'

-- Recently written scripts/executables in high-risk paths
SELECT FullPath, Size, Mtime, Ctime, Mode
FROM glob(globs=['/var/lib/gitea/custom/public/**', '/app/gitea/custom/public/**',
                 '/tmp/*.sh', '/dev/shm/*', '/var/tmp/*'])
WHERE Mtime > now() - 604800
  AND (FullPath =~ '(?i)\.(sh|php|py|jsp|war|elf)$' OR Mode =~ 'x')
ORDER BY Mtime DESC

-- Live egress from gitea process
SELECT Pid, Name, Status, LocalAddr, RemoteAddr
FROM netstat()
WHERE Name =~ '(?i)gitea'
  AND Status =~ 'ESTABLISHED'
  AND RemoteAddr !~ '^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|127\.)'
Bash / Shell
#!/usr/bin/env bash
# Gitea compromise assessment and hardening — run on every Gitea host (bare metal or container host).
# Run as root. Review output before making destructive changes.

set -euo pipefail
echo "=== [1] Gitea version check ==="
# Find the binary and report version; compare against latest stable at https://dl.gitea.com/gitea/
GITEA_BIN=$(command -v gitea || ls /usr/local/bin/gitea /app/gitea/gitea 2>/dev/null | head -1 || true)
if [ -n "$GITEA_BIN" ]; then "$GITEA_BIN" --version; else echo "gitea binary not found in PATH — check container: docker exec <container> gitea --version"; fi

echo "=== [2] Exposure check — is Gitea listening on a public interface? ==="
ss -tlnp 2>/dev/null | grep -Ei 'gitea|:3000' || echo "No direct gitea listener found (may be behind reverse proxy — check nginx/traefik configs)"

echo "=== [3] Suspicious child processes of gitea service ==="
GITEA_PID=$(pgrep -f 'gitea web' | head -1 || true)
if [ -n "$GITEA_PID" ]; then
  ps --ppid "$GITEA_PID" -o pid,ppid,user,cmd --forest
  pstree -p "$GITEA_PID" 2>/dev/null || true
else echo "gitea web process not found"; fi

echo "=== [4] Recently modified files in web-served / temp paths (last 7 days) ==="
find /var/lib/gitea/custom/public /app/gitea/custom/public /tmp /dev/shm /var/tmp \
  -type f \( -name '*.sh' -o -name '*.php' -o -name '*.py' -o -name '*.jsp' -o -perm -111 \) \
  -mtime -7 -ls 2>/dev/null || true

echo "=== [5] Git service account persistence — SSH keys and cron ==="
ls -la /home/git/.ssh/ 2>/dev/null || true
cat /home/git/.ssh/authorized_keys 2>/dev/null || echo "no authorized_keys for git user"
crontab -l -u git 2>/dev/null || echo "no crontab for git user"
ls -la /etc/cron.d/ 2>/dev/null | grep -iv '^total' || true

echo "=== [6] Unauthorized local users / recent logins ==="
awk -F: '($3 >= 1000 || $3 == 0) {print $1, $3, $6}' /etc/passwd
last -n 30 2>/dev/null || true

echo "=== [7] Egress connections from gitea ==="
ss -tnp 2>/dev/null | grep -i gitea || echo "no established gitea connections"

echo "=== [8] Upgrade path ==="
echo "Backup first: gitea dump -c /path/to/app.ini"
echo "Then deploy the latest stable release from https://dl.gitea.com/gitea/ (verify checksum/signature)"
echo "Docker: pin to the latest patched tag, e.g. docker pull gitea/gitea:latest -> recreate container"

echo "=== Assessment complete. If sections 3-6 returned unexpected results, isolate the host and initiate IR. ==="

Remediation

Treat this in two tracks: close the hole and assume breach.

Immediate (today):

  1. Inventory and verify exposure. Identify every Gitea instance — including forgotten dev boxes, Docker deployments, and instances embedded in CI tooling. Confirm whether each was internet-reachable during the campaign window. TRU's scan data (1,386 instances) tells you the actor's target list was built from internet-wide scanning: if you were listening, you were enumerated.
  2. Patch to the latest stable Gitea release from the official channel (dl.gitea.com or the gitea/gitea container image). Because public reporting on this campaign does not isolate a single CVE, apply the current security release train and review the advisories at https://github.com/go-gitea/gitea/security/advisories for the exact fixed-version floor for your deployment branch. Verify checksums and signatures before deployment.
  3. If you cannot patch within hours, take the instance off the public internet. Place it behind a VPN or IP allowlist at the reverse proxy/firewall. For a development tool, there is rarely a business justification for unauthenticated internet exposure — fix the architecture, not just the version.

Assume-breach actions (for anything that was exposed and unpatched):

  1. Run the assessment script and detections above. The key question is not "are we patched" but "were we already inside the 13." Prioritize: child processes of the gitea service, new files in custom/public and temp directories, new SSH keys for the git user, and novel egress.
  2. Rotate everything the server could touch. Gitea user credentials and access tokens, OAuth app secrets, webhook secrets, deploy keys, CI/CD pipeline variables stored in repositories, and any credentials found in private repos (assume the repo contents were exfiltrated — that is the collection objective for an actor like this). Revoke and reissue, don't just reset.
  3. Audit repository integrity. Check for unauthorized commits, force-pushes, modified branch protections, and new hooks on critical repositories — tampered source code is the supply-chain endgame.
  4. Reset password hashes and invalidate sessions via the Gitea admin panel after patching; the user table is a prime exfiltration target.
  5. Harden going forward: disable open self-registration, enforce 2FA, restrict sign-in to your IdP via OIDC/LDAP, enable fail2ban or equivalent rate limiting on auth endpoints, and ship Gitea access and audit logs to your SIEM permanently — not just during incident season.

Timeline pressure: Red Heron demonstrated scanning-to-exploitation inside a very short disclosure window. Your patch SLA for internet-facing, code-holding infrastructure should be measured in hours, not weeks. If your change-management process cannot accommodate that, pre-authorize emergency patching for this asset class now.

Related Resources

Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.