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.
4 steps from start to impact.
Get an enabled TGS account
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.- Target runs
tgstation-serverversion>= 6.11.0and< 6.12.3 - Attacker can reach the TGS API over the network
- Attacker has valid credentials for an enabled user
- 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
POST / followed by bearer-token use. Credential scanners will not reliably surface this; it is an app authz flaw, not a banner-only issue.Abuse the OR'd authorization logic
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.- Valid bearer token for an enabled user
- Requested action is one of the endpoints impacted by the broken role composition
- The bug affects most, but not all actions
- The exact reachable blast radius depends on which TGS features are configured and in use
WriteUsersis explicitly unaffected, limiting durable privilege rewrite
Read or manipulate instance resources
curl, httpie, Python requests, or the built-in Swagger/OpenAPI client surface.- Target instance is online for instance-scoped operations
- The deployment actually uses the impacted feature set, such as repository or config management
- 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
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.Cause operational damage without permanent admin takeover
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.- Attacker maintains access to a valid enabled account
- Target features provide meaningful actions to abuse
- 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
The supporting signals.
| In-the-wild status | No 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 availability | No 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. |
| EPSS | 0.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 status | Not KEV-listed as of the reviewed CISA sources. No due date or federal exploitation mandate is attached. |
| CVSS vector | CVSS: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 versions | tgstation-server >= 6.11.0, < 6.12.3 per the GitHub advisory. The issue report reproduced on 6.12.2. |
| Fixed versions | Upgrade 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 reality | Public 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 timeline | Issue 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 / reporter | The GitHub advisory credits LetterN as finder and Cyberboss for remediation. The underlying public issue #2064 was opened by LetterN. |
noisgate verdict.
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.
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-serveris 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
WriteUsersis 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.
What to do — in priority order.
- 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.
- 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.
- 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.
- 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.
- 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
curlor any HTTP client. - Leaving
WriteUserslocked down is not sufficient protection; that right is already unaffected, but the bug still opens access to many other actions.
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.
#!/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()
If you remember one thing.
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
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.