This is a bad lock on an apartment door inside a building, not an open front gate
CVE-2025-59489 is a Unity Runtime argument-injection flaw that can make a Unity-built app load a library from an unintended path. The vulnerable population is broad in theory: apps built with Unity 2017.1 and later and shipped on Android, Windows, macOS, and Linux before the October 2, 2025 fixes. Unity's own advisory says supported editor branches were fixed on 2025-10-02, while older 2017/2018 lines have no editor patch and instead need rebuild/redeploy or a runtime binary patch path.
The vendor's HIGH rating is technically understandable because the impact inside the target app can be full code execution at that app's privilege level. But for enterprise patch prioritization, reality is narrower: exploitation is fundamentally local or adjacent to local launch/control, there is no KEV listing, Unity says there was no evidence of exploitation, and the EPSS is effectively floor-level. That combination pushes this down from urgent enterprise-wide fire drill to a controlled inventory-and-remediation exercise.
4 steps from start to impact.
Get a way to launch or influence the app locally
- Attacker has code execution on the same device, or can install/run a local helper app
- Or a Windows deployment exposes a custom URI handler tied to the Unity app
- This is not a generic unauthenticated network service
- Enterprise endpoints often do not expose Unity apps through browser-reachable custom URI handlers
- Mobile exploitation generally implies a second malicious app is already present
Inject Unity startup arguments
unity extra and the -xrsdk-pre-init-library path as the abuse primitive. Weaponized tool: crafted Intent or crafted launch string based on the documented startup-argument feature.- The target app was built with a vulnerable Unity runtime
- The attacker can supply startup arguments or equivalent launch metadata
- Platform behavior differs by OS
- Many apps will never be launched from attacker-controlled wrappers or URIs
- Some OS hardening and app packaging choices reduce reachable argument paths
Force library loading from an attacker-controlled path
.so/DLL/dylib payload referenced by xrsdk-pre-init-library as described in the research write-up.- Attacker can place or reference a loadable library in a reachable path
- The platform's loading rules permit the target path
- Library placement is another prerequisite beyond merely reaching the app
- OS controls such as SELinux, code-signing expectations, app sandboxing, and filesystem permissions can reduce practical success
- Execution stays inside the app's privilege boundary
Operate as the compromised app
- Successful module load into the vulnerable app
- Target app has useful data, permissions, or brokered access
- Blast radius is usually limited to one app and one user context
- No automatic privilege escalation to SYSTEM/root is documented
- Value depends heavily on what that specific Unity app can access
The supporting signals.
| In-the-wild status | Unity states there was no evidence of exploitation and no user/customer impact in its advisory; the CVE is not KEV-listed. |
|---|---|
| Proof-of-concept availability | There is a public technical write-up by RyotaK of GMO Flatt Security Inc. with exploitation details, and NVD links it with the Exploit tag. Public GitHub detection tooling also exists, e.g. taptap/cve-2025-59489. |
| EPSS | 0.017% (4th percentile) per the GitHub advisory's FIRST-fed EPSS display — extremely low short-term exploitation probability. |
| KEV status | No. No CISA KEV entry is associated with this CVE in the public catalog. |
| CVSS split | There is a scoring mismatch: Unity advisory = 8.4 / AV:L/AC:L; the user-provided 7.4 / AV:L/AC:H aligns with the lower MITRE/NVD primary view. For defenders, the important constant is still AV:L. |
| Affected versions | Unity says apps built with affected Unity Editor versions from 2017.1 onward are vulnerable on Android, Windows, macOS, Linux Desktop, and Linux Embedded if shipped before the fixes on 2025-10-02. |
| Fixed versions | Supported branches received first fixed versions such as 2019.1.15f1, 2021.3.56f2, 2022.3.67f2, 6000.0.58f2, 6000.1.17f1, 6000.2.6f2, 6000.3.0b4. Older 2017/2018 lines show N/A for editor patching in the advisory and may need runtime binary patching or application rebuild/redeployment. |
| Exposure/scanning reality | This is not an internet-listening service. Shodan/Censys-style exposure counts are a poor proxy; you need endpoint software inventory, SBOMs, mobile app catalogs, or build metadata, not perimeter scans. |
| Platform nuance | Android carries the clearest impact path because a malicious on-device app can abuse Unity's argument handling and inherit the target app's permissions. Windows risk increases if the Unity app registers a custom URI handler that an attacker can cause to open. |
| Disclosure and researcher | Discovered on 2025-06-04 by RyotaK / GMO Flatt Security Inc.; patch available 2025-10-02; public disclosure 2025-10-03. |
noisgate verdict.
The single biggest downward pressure is attacker position: this is overwhelmingly a local/post-initial-access bug, not a remotely reachable enterprise edge flaw. The broad affected population matters, but without a local foothold, malicious helper app, or special launch path such as a Windows custom URI handler, most real-world enterprise deployments are not directly exposed.
Why this verdict
- AV:L is the whole story: the attack starts from local execution, a second malicious app, or a special local launch vector. That is immediate downward pressure from the vendor baseline.
- Post-initial-access prerequisites compound: needing on-device code, install rights, or control of a launch/URI path implies the attacker is already on the box or device. EDR, MTD, app allowlisting, and browser controls should break a lot of the chain.
- No exploitation evidence and floor-level EPSS: Unity reports no observed exploitation, it is not in KEV, and EPSS is 0.017%. That does not make it harmless, but it does argue against emergency-tier prioritization.
Why not higher?
There is no unauthenticated remote entry point here. The exploit chain depends on local execution conditions and app-specific launch semantics, which sharply narrows the exposed population compared with edge RCE or wormable client bugs.
Why not lower?
The impact after successful exploitation is still real: arbitrary code can run inside the target app's trust boundary, and on Android that can mean piggybacking on the victim app's granted permissions. Unity's install base is enormous, so even a local-only bug can matter if you have a meaningful population of Unity-built apps on managed endpoints or mobile fleets.
What to do — in priority order.
- Inventory Unity-built applications — Build a host and mobile app inventory that identifies Unity runtime usage, starting with Windows desktops, managed macOS/Linux endpoints, and Android fleets. Because this is a MEDIUM verdict, there is no mitigation SLA — go straight to the 365-day remediation window, but do the inventory work now so you can separate supported branches from legacy 2017/2018 stragglers.
- Constrain untrusted app installation — On Android especially, the exploit value depends on a second malicious app or equivalent local foothold. Tighten enterprise app-store controls, sideloading restrictions, and app allowlisting to reduce the odds of the prerequisite being satisfied before the 365-day remediation window closes.
- Audit custom URI handlers — On Windows, Unity explicitly notes custom URI handlers can increase exploitability. Identify Unity-based apps that register schemes, limit which apps may invoke them, and review browser/email launch policies; do this during normal hardening work because there is no mitigation SLA for a MEDIUM finding.
- Use EDR to watch module loads and suspicious launch chains — Create detections for Unity-based processes loading unusual DLLs/shared libraries or being launched from odd parents, wrappers, or URIs. This is the practical compensating control when you cannot immediately obtain a rebuilt vendor package, and it should stay in place through the 365-day remediation window.
- Press vendors for rebuilt packages — If you rely on third-party Unity applications, ask the publisher for the rebuilt version or proof they applied Unity's runtime patching path. Unsupported 2017/2018 branches are the ugly exception, because the advisory shows N/A editor fixes there even though the vulnerability remains relevant.
- Perimeter vulnerability scans: this is not a network service, so internet-facing scanners will give you false comfort.
- MFA: it helps with identity abuse, not with a malicious local app or local launch path feeding arguments into a Unity runtime.
- Just patching the Unity Editor on a developer workstation: deployed apps remain vulnerable until the application is rebuilt/redeployed or the runtime library is actually replaced.
Crowdsourced verification payload.
Run this on an auditor workstation, CI runner, or packaging host where you have a CSV inventory of Unity app versions or know the exact embedded Unity version for a target app. Invoke it as python3 check_unity_cve_2025_59489.py --version 2022.3.61f1 for a single app, or python3 check_unity_cve_2025_59489.py --csv unity_inventory.csv --column unity_version; no admin rights are required.
#!/usr/bin/env python3
# check_unity_cve_2025_59489.py
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN, 3=usage/runtime error
import argparse
import csv
import re
import sys
from typing import Optional, Tuple
# First fixed versions from Unity advisory for supported branches.
FIXED = {
"2019.1": "2019.1.15f1",
"2019.2": "2019.2.23f1",
"2019.3": "2019.3.17f1",
"2019.4": "2019.4.41f1",
"2020.1": "2020.1.18f1",
"2020.2": "2020.2.8f1",
"2020.3": "2020.3.49f1",
"2021.1": "2021.1.29f1",
"2021.2": "2021.2.20f1",
"2021.3": "2021.3.45f2",
"2022.1": "2022.1.25f1",
"2022.2": "2022.2.23f1",
"2022.3": "2022.3.62f2",
"2023.1": "2023.1.22f1",
"2023.2": "2023.2.22f1",
"6000.0": "6000.0.58f2",
"6000.1": "6000.1.17f1",
"6000.2": "6000.2.6f2",
"6000.3": "6000.3.0b4",
}
# Legacy lines noted by Unity as affected but with N/A editor patched versions.
# Version-only checking cannot prove binary-patch status, so these are conservatively VULNERABLE.
LEGACY_VULN_PREFIXES = [
"2017.1",
"2017.2",
"2017.3",
"2017.4",
"2018.1",
"2018.2",
"2018.3",
"2018.4",
]
VER_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)([abfp])(\d+)$")
SUFFIX_ORDER = {"a": 0, "b": 1, "f": 2, "p": 3}
def parse_version(v: str) -> Optional[Tuple[int, int, int, int, int]]:
v = v.strip()
m = VER_RE.match(v)
if not m:
return None
major, minor, patch, suffix, suffix_num = m.groups()
return (int(major), int(minor), int(patch), SUFFIX_ORDER[suffix], int(suffix_num))
def branch_of(v: str) -> Optional[str]:
m = re.match(r"^(\d+)\.(\d+)\.", v.strip())
if not m:
return None
return f"{m.group(1)}.{m.group(2)}"
def compare_versions(a: str, b: str) -> Optional[int]:
pa = parse_version(a)
pb = parse_version(b)
if pa is None or pb is None:
return None
return (pa > pb) - (pa < pb)
def assess(version: str) -> Tuple[str, str]:
version = version.strip()
branch = branch_of(version)
if not branch:
return ("UNKNOWN", f"Unrecognized Unity version format: {version}")
if branch in LEGACY_VULN_PREFIXES:
return (
"VULNERABLE",
f"{version} is in legacy affected branch {branch}; Unity lists no editor patched version (N/A). Assume vulnerable unless you have explicit proof of runtime binary patching or rebuilt application.",
)
fixed = FIXED.get(branch)
if not fixed:
return (
"UNKNOWN",
f"Branch {branch} is not in the built-in advisory map. Check the Unity advisory manually.",
)
cmp_res = compare_versions(version, fixed)
if cmp_res is None:
return ("UNKNOWN", f"Could not compare {version} to fixed version {fixed}")
if cmp_res < 0:
return ("VULNERABLE", f"{version} is older than fixed version {fixed} for branch {branch}")
return ("PATCHED", f"{version} is at or newer than fixed version {fixed} for branch {branch}")
def process_single(version: str) -> int:
status, detail = assess(version)
print(status)
print(detail)
return {"PATCHED": 0, "VULNERABLE": 1, "UNKNOWN": 2}[status]
def process_csv(path: str, column: str) -> int:
worst = 0
seen = 0
with open(path, newline="", encoding="utf-8-sig") as fh:
reader = csv.DictReader(fh)
if column not in reader.fieldnames:
print("UNKNOWN")
print(f"CSV missing required column: {column}")
return 2
for row in reader:
seen += 1
version = (row.get(column) or "").strip()
asset = row.get("asset") or row.get("name") or row.get("app") or f"row-{seen}"
if not version:
status, detail = ("UNKNOWN", "No Unity version present")
else:
status, detail = assess(version)
print(f"{asset},{version},{status},{detail}")
rc = {"PATCHED": 0, "VULNERABLE": 1, "UNKNOWN": 2}[status]
worst = max(worst, rc)
if seen == 0:
print("UNKNOWN")
print("CSV contained no rows")
return 2
print(["PATCHED", "VULNERABLE", "UNKNOWN"][worst if worst < 3 else 2])
return worst
def main() -> int:
parser = argparse.ArgumentParser(description="Check Unity versions for CVE-2025-59489 exposure")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--version", help="Single Unity version, e.g. 2022.3.61f1")
group.add_argument("--csv", help="CSV file with Unity versions")
parser.add_argument("--column", default="unity_version", help="CSV column name for the Unity version (default: unity_version)")
args = parser.parse_args()
try:
if args.version:
return process_single(args.version)
return process_csv(args.csv, args.column)
except FileNotFoundError as e:
print("UNKNOWN")
print(f"File not found: {e}")
return 3
except Exception as e:
print("UNKNOWN")
print(f"Runtime error: {e}")
return 3
if __name__ == "__main__":
sys.exit(main())
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.