← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2025-21611 · CWE-285 · Disclosed 2025-01-06

tgstation-server is a production scale tool for BYOND server management

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

This is a side door that opens for anyone already holding a visitor badge

CVE-2025-21611 is an authorization bug in tgstation-server where the global enabled user check was combined with per-action roles using OR logic instead of AND. In affected versions >= 6.11.0, < 6.12.3, any already-enabled account can reach most API actions even when its assigned rights should block them. The vendor explicitly notes WriteUsers is unaffected, so the bug does not let a low-privileged user permanently rewrite their own account permissions.

The vendor's HIGH 8.8 score is technically defensible in CVSS terms because exploitation is remote and low-complexity once you have an account. In real operations, though, the decisive friction is that this is not initial access: the attacker must already possess a valid, enabled TGS identity in a niche server-management product with limited enterprise exposure. That makes this a meaningful authz failure, but not the sort of internet-scale emergency the raw CVSS suggests.

"High on paper, but in practice this is a post-auth app-level privilege jump in a niche admin plane"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Get an enabled TGS account

The attacker first needs a legitimate tgstation-server identity that can successfully authenticate and is marked enabled. In practice this means a stolen operator password, an over-provisioned service account, shared admin credentials, or insider access. Weaponized tooling here is trivial: browser login, curl, httpie, or any script that can post Basic auth to the API root and receive a bearer token.
Conditions required:
  • Target runs tgstation-server version >= 6.11.0 and < 6.12.3
  • Attacker can reach the TGS API over the network
  • Attacker has valid credentials for an enabled user
Where this breaks in practice:
  • This is authenticated remote, not unauthenticated internet exploitation
  • Many deployments likely have very small user populations, reducing attacker opportunity
  • Disabled accounts do not work, and some environments may front TGS with VPN or reverse-proxy access controls
Detection/coverage: Identity and reverse-proxy logs should show successful login to POST / followed by bearer-token use. Credential scanners will not reliably surface this; it is an app authz flaw, not a banner-only issue.
STEP 02

Abuse the OR'd authorization logic

Once authenticated, the attacker calls endpoints that should require specific roles. The vulnerable code appended the global UserEnabledRole into role evaluation in a way that effectively treated enabled as sufficient for many actions; the fix moved this to a separate policy so action roles and enabled-state are both enforced. The public issue shows a concrete example: a user without RepositoryRights.Read could still read /Repository.
Conditions required:
  • Valid bearer token for an enabled user
  • Requested action is one of the endpoints impacted by the broken role composition
Where this breaks in practice:
  • The bug affects most, but not all actions
  • The exact reachable blast radius depends on which TGS features are configured and in use
  • WriteUsers is explicitly unaffected, limiting durable privilege rewrite
Detection/coverage: Look for low-privilege or non-admin accounts suddenly invoking repository, config, job, compile, or instance-management APIs they normally never touch. App audit logs are more valuable than network IDS here.
STEP 03

Read or manipulate instance resources

If the exposed instance is online and feature-rich, the attacker can pivot from simple unauthorized reads into operational abuse: repository inspection, git operations, compile jobs, bot/config interaction, or service disruption. The TGS API exposes repository checkout and update actions, static-file access, and compile orchestration, so the impact can span confidentiality, integrity, and availability inside the managed game-server environment. Weaponized tools remain boring and reliable: curl, httpie, Python requests, or the built-in Swagger/OpenAPI client surface.
Conditions required:
  • Target instance is online for instance-scoped operations
  • The deployment actually uses the impacted feature set, such as repository or config management
Where this breaks in practice:
  • Not every deployment enables every subsystem
  • Some static-file access can be constrained by system-user/ACL modes
  • This is still mostly bounded to the TGS-managed environment rather than arbitrary enterprise-wide control
Detection/coverage: Monitor for unexpected GET /Repository, POST /Repository, PUT /DreamMaker, config-file reads, or admin-style jobs issued by ordinary accounts. EDR may only see downstream effects if compile or service actions spawn host processes.
STEP 04

Cause operational damage without permanent admin takeover

The practical end state is a near-admin experience for many workflows, but with an important limiter: the attacker cannot use this bug alone to grant themselves WriteUsers and make the privilege change durable. That keeps the abuse serious but somewhat session-bound and app-bounded. Impact is therefore best described as broad unauthorized action surface inside TGS, not a universal host-root or tenant-to-enterprise break.
Conditions required:
  • Attacker maintains access to a valid enabled account
  • Target features provide meaningful actions to abuse
Where this breaks in practice:
  • No permanent account-rights rewrite via WriteUsers
  • Impact depends on what TGS is actually trusted to manage
  • Small and niche deployment base reduces broad campaign value
Detection/coverage: Revoking the account or rotating credentials shuts off the path. Session and API audit trails should make post-incident scoping feasible if logging is enabled.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo public evidence of active exploitation found in the sources reviewed. CISA's January 13, 2025 weekly bulletin lists the CVE, but there is no KEV entry and no campaign reporting tied to this flaw.
Proof-of-concept availabilityNo standalone public exploit repo located. Reproduction is straightforward from the vendor's own issue #2064 plus the fix commit e7b1189; this is an easy curl/API-client bug, not a memory-corruption exploit.
EPSS0.00407 from the user-supplied intel, which is very low in absolute terms. *Inference:* that places it far from the cluster of routinely exploited flaws even without a verified percentile in the retrieved source set.
KEV statusNot KEV-listed as of the reviewed CISA sources. No due date or federal exploitation mandate is attached.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H = remote, easy, no user interaction, but requires low privileges first. That PR:L is the whole story here: you already need a foothold inside the app.
Affected versionstgstation-server >= 6.11.0, < 6.12.3 per the GitHub advisory. The issue report reproduced on 6.12.2.
Fixed versionsUpgrade to 6.12.3 or later. I found no authoritative distro backport advisory in the reviewed source set; treat upstream versioning as the patch truth unless your packaging team documents a backport.
Exposure / scanning realityPublic exposure looks niche. The product docs show an HTTP API commonly on port 5000, but I found no authoritative internet-scale fingerprint or count from Shodan/Censys/FOFA in the reviewed primary sources; plan as a low-population admin-plane issue, not a mass-exposure event.
Disclosure timelineIssue opened 2025-01-03, GitHub advisory published 2025-01-04, CVE/NVD/CISA publication landed 2025-01-06, and CISA weekly bulletin referenced it on 2025-01-13.
Researcher / reporterThe GitHub advisory credits LetterN as finder and Cyberboss for remediation. The underlying public issue #2064 was opened by LetterN.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (6.3/10)

The single biggest severity reducer is the prerequisite chain: this flaw requires authenticated access to an enabled account in a niche management plane before anything bad happens. That's a real privilege-escalation bug inside the app, but it is post-initial-access and lacks the exposure, exploit pressure, and permanence that justify a HIGH queue position for most enterprise patch programs.

HIGH Affected-version and fixed-version determination
MEDIUM Real-world blast-radius estimate across diverse TGS deployments
HIGH Assessment that this is not currently a KEV or active-exploitation case

Why this verdict

  • Dropped for attacker position: vendor CVSS starts from PR:L, which in real life means the attacker already has a valid, enabled account in the application. That is a post-auth control failure, not internet-scale initial access.
  • Dropped for population: tgstation-server is a specialized BYOND server-management product, not a broadly deployed enterprise staple. Narrow deployment base means lower campaign utility and lower expected opportunistic exploitation.
  • Dropped for durability limits: the advisory explicitly says WriteUsers is unaffected, so the attacker cannot use this bug alone to permanently rewrite their account rights. That trims the persistence and administrative finality of the escalation.
  • Held above LOW because impact is still broad once inside: the bug opens access to most authorized API actions, and the public issue shows a real rights bypass on /Repository. On a live TGS instance, that can translate into material confidentiality, integrity, and availability impact.
  • Held below HIGH because there is no exploitation pressure: no KEV listing, no public campaign evidence, no public exploit kit, and a very low EPSS all push this out of the urgent queue.

Why not higher?

Because every exploit chain starts with an already-authenticated, enabled TGS user, this vulnerability assumes a prior compromise stage or an insider. That prerequisite sharply narrows reachable population and should be treated as compounding downward pressure on severity. The lack of public exploitation evidence and the product's niche footprint remove the urgency you would expect from a truly high-priority remote admin-plane bug.

Why not lower?

This is still not harmless. The flaw collapses permission boundaries for most API actions inside a server-management plane, which is exactly the kind of place where low-privileged users should never get broad operational reach. If your TGS deployment is shared among moderators, content managers, or junior operators, the practical blast radius is too meaningful to dismiss as backlog noise.

05 · Compensating Control

What to do — in priority order.

  1. Restrict API reachability — Put the TGS API behind VPN, bastion, or tightly scoped reverse-proxy ACLs so only operator networks can reach it. For a MEDIUM verdict there is no mitigation SLA — go straight to the 365-day remediation window, but if your TGS endpoint is internet-accessible, do this immediately anyway because it shrinks the attacker pool to insiders and already-authorized admins.
  2. Disable unnecessary enabled users — The bug only works for accounts that are both valid and enabled, and the vendor workaround says to disable users that should not have full access. Prune stale local users, shared accounts, and dormant service identities during the normal remediation window.
  3. Review low-privileged instance users — Audit who has any instance access at all, especially accounts intended to have narrow repo or job permissions. For a MEDIUM issue there is no noisgate mitigation deadline, but rights cleanup is worth doing before the eventual patch because it removes the exact exploitation substrate.
  4. Alert on non-admin API use of admin-like endpoints — Create detections for low-privileged or rarely used accounts hitting repository, compile, config, or instance-management endpoints. This gives you visibility during the 365-day remediation window and helps catch both abuse of this CVE and ordinary credential misuse.
What doesn't work
  • A WAF alone does not fix broken server-side authorization logic; the requests are legitimate API calls from authenticated users.
  • Hiding the UI does not help because the issue is in API authorization, and the API is scriptable with curl or any HTTP client.
  • Leaving WriteUsers locked down is not sufficient protection; that right is already unaffected, but the bug still opens access to many other actions.
06 · Verification

Crowdsourced verification payload.

Run this from an auditor workstation or the target host. Preferred invocation is against the unauthenticated TGS root endpoint documented as GET /: python3 tgs_cve_2025_21611_check.py --url http://tgs.example:5000. No elevated privileges are required; alternatively pass a known version directly with --version 6.12.2.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# CVE-2025-21611 checker for tgstation-server
# Usage:
#   python3 tgs_cve_2025_21611_check.py --url http://tgs.example:5000
#   python3 tgs_cve_2025_21611_check.py --version 6.12.2
# Exit codes:
#   0 = PATCHED
#   1 = VULNERABLE
#   2 = UNKNOWN

import argparse
import json
import re
import sys
import urllib.request
import urllib.error

AFFECTED_MIN = (6, 11, 0)
PATCHED_MIN = (6, 12, 3)


def parse_version(text):
    if text is None:
        return None
    m = re.search(r'(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?', str(text))
    if not m:
        return None
    return tuple(int(x) for x in m.groups(default='0'))


def cmp_ver(a, b):
    maxlen = max(len(a), len(b))
    a = a + (0,) * (maxlen - len(a))
    b = b + (0,) * (maxlen - len(b))
    if a < b:
        return -1
    if a > b:
        return 1
    return 0


def classify(ver):
    if cmp_ver(ver, AFFECTED_MIN) >= 0 and cmp_ver(ver, PATCHED_MIN) < 0:
        return 'VULNERABLE'
    if cmp_ver(ver, PATCHED_MIN) >= 0:
        return 'PATCHED'
    return 'PATCHED'


def fetch_version_from_url(base_url, timeout=8):
    url = base_url.rstrip('/') + '/'
    req = urllib.request.Request(url, headers={'User-Agent': 'noisgate-cve-check/1.0', 'Accept': 'application/json'})
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        raw = resp.read().decode('utf-8', errors='replace')
        try:
            data = json.loads(raw)
        except json.JSONDecodeError:
            return None, f'Non-JSON response from {url}'
        if isinstance(data, dict):
            version = data.get('version')
            if version:
                return str(version), None
            return None, f'JSON response from {url} did not contain a version field'
        return None, f'Unexpected JSON structure from {url}'


def main():
    parser = argparse.ArgumentParser(description='Check tgstation-server for CVE-2025-21611 exposure by version.')
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('--url', help='Base URL of the tgstation-server API, e.g. http://host:5000')
    group.add_argument('--version', help='Known tgstation-server version string, e.g. 6.12.2')
    args = parser.parse_args()

    version_text = None

    if args.url:
        try:
            version_text, err = fetch_version_from_url(args.url)
            if err:
                print(f'UNKNOWN - {err}')
                sys.exit(2)
        except urllib.error.HTTPError as e:
            print(f'UNKNOWN - HTTP error querying {args.url}: {e.code} {e.reason}')
            sys.exit(2)
        except urllib.error.URLError as e:
            print(f'UNKNOWN - Network error querying {args.url}: {e.reason}')
            sys.exit(2)
        except Exception as e:
            print(f'UNKNOWN - Unexpected error querying {args.url}: {e}')
            sys.exit(2)
    else:
        version_text = args.version

    ver = parse_version(version_text)
    if ver is None:
        print(f'UNKNOWN - Could not parse version from input: {version_text}')
        sys.exit(2)

    status = classify(ver)
    normalized = '.'.join(str(x) for x in ver[:4]).rstrip('.0') if len(ver) > 3 else '.'.join(str(x) for x in ver)

    if status == 'VULNERABLE':
        print(f'VULNERABLE - tgstation-server version {normalized} is within affected range >= 6.11.0 and < 6.12.3')
        sys.exit(1)
    elif status == 'PATCHED':
        print(f'PATCHED - tgstation-server version {normalized} is not within the affected range for CVE-2025-21611')
        sys.exit(0)

    print('UNKNOWN - Unable to determine status')
    sys.exit(2)


if __name__ == '__main__':
    main()
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: identify whether you run tgstation-server at all, then inventory versions via the unauthenticated root API and flag anything from 6.11.0 through 6.12.2. For this MEDIUM reassessment there is no noisgate mitigation SLA — go straight to the 365-day remediation window, so plan upgrade to 6.12.3+ inside the noisgate remediation SLA of ≤ 365 days; if your TGS API is internet-reachable or shared among non-admin operators, tighten access and disable unnecessary enabled users immediately as a local risk override even though the formal bucket is not urgent.

Sources

  1. GitHub Security Advisory GHSA-rf5r-q276-vrc4
  2. Fix commit e7b1189
  3. Public issue #2064 reproduction details
  4. NVD CVE-2025-21611
  5. tgstation-server API documentation
  6. tgstation-server product documentation
  7. CISA Vulnerability Summary for the Week of January 6, 2025
  8. FIRST EPSS API documentation
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.