GCVE-1988-2026-0505
Vulnerability from gna-1988 – Published: 2026-11-02 07:30 – Updated: 2026-11-02 07:30| Vendor | Product | Version | CPE status | |
|---|---|---|---|---|
| Nuuo | Crystal Server |
Affected:
unknown
|
guessed |
{
"containers": {
"cna": {
"affected": [
{
"product": "Crystal Server",
"vendor": "Nuuo",
"versions": [
{
"status": "affected",
"version": "unknown"
}
]
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "disclosure via Fulldisclosure"
}
],
"descriptions": [
{
"lang": "en",
"value": "0day Rubbish Research Team is publicly disclosing a vulnerability in NUUO\nCrystal Server 3.19.0.85, the video management and NVR server software from\nNUUO Inc. of Taipei (Taiwan), distributed as the Crystal virtual-machine\nappliance: an Ubuntu-based OVA whose web tier is a lighttpd server fronting\na PHP application, with .php requests dispatched to /usr/bin/php-cgi through\nlighttpd\u0027s fastcgi mapping. The product sits in the management plane of\nphysical-security and critical-infrastructure video deployments, brokering\ncamera registration, recorder channels and the stored management credentials\nof the surveillance estate.\n\nType and root cause. Inside the web root recovered from the appliance disk\nimage, upload.php is three lines long, and the brevity is the finding: it\nechoes the client-supplied filename and calls copy() with a destination\ntaken from that same client-supplied filename. There is no session include,\nno login check, no extension filter, no MIME check, no path restriction and\nno storage directory of the product\u0027s own choosing. The web server\nconfiguration recovered from the same disk image loads no authentication\nmodule at all, because this product\u0027s architecture delegates authentication\nto each PHP file individually, and upload.php is one of the files that\nnever asks. Three omissions stack into one chain, each independently\nsufficient to make the write dangerous: missing authentication for a\ncritical function (CWE-306 primary), an upload accepted without type\nrestriction into an executable mapping (CWE-434 co-equal), and external\ncontrol of the destination file name or path (CWE-73 contributing).\n\nThe chain. An anonymous client on any network that can reach the management\ninterface posts a multipart body carrying a PHP payload whose disposition\nfilename is the destination of the write. copy() writes the file under the\nattacker-chosen name into the served document root /NU20/web/, the HTTPS\nlistener of the recovered configuration binding :443 across addresses with\nno restriction. One fetch of the written path then hands the file to the\nproduct\u0027s own fastcgi mapping, and the chosen PHP executes in the product\u0027s\nweb service context. Two requests, no credential at any point, no memory\ncorruption, no deserialisation gadget, no default credential and no action\nby any user of the product. The write and the execution are decoupled in\ntime, which makes the write primitive independently useful: served pages\ncan be replaced, configuration and application files inside the web root\ncan be overwritten, and a persistent implant can be dropped into a location\nthe product itself serves, surviving a service restart as an ordinary file\nin the served tree until an operator finds it.\n\nEverything in the chain is a shipped default, established from artifacts\nrather than from lab state: the endpoint exists in the shipped web root\n(squashfs extraction, byte-exact), the endpoint requires no authentication\n(absent include plus absent server-layer gate, both artifacts), the\ndestination is client-controlled (the copy() second argument is the\nmultipart filename, from the endpoint source), the destination directory\nis served and executable (document root and fastcgi mapping, both from the\nrecovered configuration), and the listener accepts connections from any\naddress.\n\nThe authentication model, named for what it is. The recovered lighttpd\nconfiguration declares its module list explicitly and loads no\nauthentication module; a content search of the recovered configuration for\nan authentication backend, an authentication module, a valid-user\nrequirement and an access-denial rule returns zero hits for all four\npatterns. The gate, where it exists at all, is one include statement inside\neach PHP file, so the product\u0027s authentication boundary is exactly as\nstrong as its most forgettable file, and upload.php is that file. The\nboundary claim is deliberately conservative: this handler is established\nas unauthenticated by the byte-exact source plus the absent server-layer\ngate, and no claim is made about other handlers in the web root, which are\nnamed in the advisory as an audit recommendation, not as findings.\n\nVerification boundary, stated in full, because it defines what is and is not\nbeing claimed. No physical Crystal VM was reachable during this study, so\nthe verification could not be a live drive against the product\u0027s own\nlistener. The static half is anchored in the appliance disk image: the\nOVA\u0027s VMDK was converted to a raw image, the PHP web root was carved out of\nits squashfs region (653 inodes, 575 files, including the byte-exact three-\nline upload.php), and the server configuration was recovered from the same\ndisk and read in full. The dynamic half ran on a faithful replica: the\nappliance\u0027s own extracted web root, all recovered files including the real\nupload.php, served with the document-root behaviour and the\nunauthenticated-execution semantics of the appliance\u0027s own extracted\nconfiguration, on the loopback interface of an isolated research host.\nThree independent runs uploaded the probe file without any credential,\nconfirmed the landing in the document root, and returned an execution\nmarker together with command output on one fetch; a no-upload negative\ncontrol produced no marker (and a fetch of never-uploaded names returned\n404 with nothing); and an absolute-destination variant, writing to an\nabsolute path inside the served root, landed and executed identically, so\nthe write does not depend on a working-directory coincidence. The exact\nworking-directory resolution of a relative destination on a shipped\nappliance is the one piece this work could not observe on physical hardware,\nwhich is why the absolute-destination result exists and why the chain does\nnot rest on it.\n\nOn the executing identity, because it matters for what downstream readers\nshould and should not claim. The command output in the replica runs\nidentified the invoking account of the replica\u0027s PHP server process. That\nmeasurement is a property of the replica environment and is reported as\nsuch: it demonstrates that executed code inherits the identity of the PHP\nservice. The corresponding identity on a shipped appliance, the lighttpd\n/ php-cgi service account of the VM, was not observed at runtime in this\nwork, and no superuser claim appears anywhere in the advisory. The\nexecution primitive, the score and the impact statements are written to be\ntrue whichever account that turns out to be; a reader who sees a 9.8 vector\nand starts writing \"root on the appliance\" is jumping past the published\nevidence, and this disclosure says so plainly rather than benefiting from\nthe inflation.\n\nScoring. One score is published: 9.8 Critical,\nCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, re-derived rather than\ninherited. The arithmetic is printed so any reader can reproduce it under\nthe CVSS 3.1 base algorithm and its round-up-to-the-tenth rule: ISS =\n1 - (0.44 x 0.44 x 0.44) = 0.914816; Impact for Scope Unchanged = 6.42 x\n0.914816 = 5.869119; Exploitability = 8.22 x 0.85 x 0.77 x 0.85 x 0.85 =\n3.887043; the unrounded base is 9.756162 and Roundup gives 9.8. A\nscope-changed reading that would compute to 10.0 is published as rejected:\nno violation of an authority beyond the web service itself was\ndemonstrated, the uploaded file executing in the product\u0027s own PHP process\ncontext, and whether that process\u0027s account additionally holds host\nprivileges is an unmeasured deployment property the advisory declines to\nclaim. A conditional reading at 8.8 High (from Exploitability 2.835255 and\nsum 8.704374) applies to deployments that confine the appliance to a\nsegmented management network; the shipped recovered configuration\nestablishes no such confinement, so the published score uses the measured\nnetwork bind and the conditional is labelled, not measured.\n\nImpact. Server-side code execution on the video management server through\nthe product\u0027s own fastcgi mapping, with no payload delivery beyond plain\nHTTP or HTTPS and no credential at any point, plus an anonymous arbitrary\nfile write into the served tree that persists across service restarts.\nWhat the web service account reaches is exposed to attacker-chosen code:\nthe application\u0027s own state, recorded media indices, camera trees, applied\nconfiguration, and whatever the service account can read on the appliance\nfilesystem, with arbitrary modification of served content and complete\ncontrol of service availability. Detection is hard by construction: both the\nwrite and the execution use the product\u0027s handled paths and ordinary\nstatus codes, leaving file-integrity monitoring of the web root and\nconfiguration-change review as the defenders\u0027 strongest signals. Two\nconsequence classes are plausible and explicitly not claimed, because they\nwere not measured: takeover of downstream cameras and recorders through\nstored management credentials, and lateral movement through the camera\nnetwork. The amplifying architecture weakness is reported alongside the\nfinding and not folded into the score: authentication implemented\nfile-by-file means every PHP handler that forgets the session include is\nunauthenticated by design, and upload.php is the one measured here.\n\nMitigation summary: remove upload.php or place its function behind the\nproduct\u0027s authenticated session gate, which severs the chain at request\none; never build a storage destination from request data, generating the\nstored name server-side from an allow-listed extension set and\ncanonicalising the resolved destination against an intended storage root;\nstore uploads outside the served tree in a non-executable quarantine\ndirectory with randomized names; move authentication from the file level to\nthe server or framework level so one forgotten include cannot reopen an\nanonymous handler, and while that migration is pending mechanically audit\nevery handler in the web root for the missing-include pattern, which in\nthis product is a grep-sized task; add a shipped-image manifest of the\ndocument root reconciled at boot or on a schedule, a day-scale engineering\neffort for an appliance that detects this class of implant; and add\nregression tests expressing the three omissions as assertions: an\nunauthenticated upload with a .php destination rejected, a session-less\nwrite to any handler rejected, and the stored destination differing from\nthe client-supplied name. For operators until a patch ships: restrict\nmanagement reachability to a segmented network, run file-integrity\nmonitoring on the document root, and treat unexpected served-content\nchanges as a standing signal.\n\nThe published proof of concept is Python 3 using only the standard library.\nIt performs one credential-less multipart POST carrying the probe file with\na chosen destination name and one fetch of that name, printing the\nresponse; the success criterion is an execution marker in the fetched body,\nso a servable-but-not-executing write cannot false-positive. A control mode\nperforms no upload and asserts nothing executes, mirroring the negative\ncontrol used during verification. The command the probe runs is supplied by\nthe operator and the published default is a non-destructive identity query.\nNo reverse shell, no callback channel, no persistence installer and no\ndestructive command is provided or was run.\n\nFull technical analysis and the reproducible proof of concept, published\nunconditionally:\n https://0day-rubbish.com/blog/nuuo-crystal-server-unauth-upload-php-webroot-write-rce\n\nPoC location in the public repository:\nproduct/nuuo/crystal-server-3.19.0.85/unauth-upload-php-webroot-write-rce/exploit/nuuo_crystal_server_unauth_upload_php_webroot_write_rce.py\n\nProject archive (ongoing disclosure series):\n https://github.com/Exploit-Garbage/0day-Rubbish\n\nThe vendor publishes no RFC 9116 security.txt: the well-known path returns\nthe site\u0027s own 404 on the www host and on the apex, as does the\nnon-well-known fallback path, and the vendor\u0027s own page index contains no\nsecurity, vulnerability-reporting or responsible-discl"
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-306",
"description": "CWE-306",
"lang": "en",
"type": "CWE"
},
{
"cweId": "CWE-434",
"description": "CWE-434",
"lang": "en",
"type": "CWE"
},
{
"cweId": "CWE-73",
"description": "CWE-73",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-11-02T07:30:04Z",
"orgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
"shortName": "VULNARCHIVE"
},
"references": [
{
"tags": [
"technical-description",
"exploit"
],
"url": "https://vuln.freearchive.org/archive/full-disclosure/2026/Oct/56"
},
{
"tags": [
"technical-description"
],
"url": "https://seclists.org/fulldisclosure/2026/Oct/56"
},
{
"url": "https://0day-rubbish.com"
},
{
"url": "https://0day-rubbish.com/blog/nuuo-crystal-server-unauth-upload-php-webroot-write-rce"
},
{
"url": "https://github.com/Exploit-Garbage/0day-Rubbish"
},
{
"url": "https://nmap.org/mailman/listinfo/fulldisclosure"
},
{
"url": "https://seclists.org/fulldisclosure/"
}
],
"source": {
"defect": [
"https://seclists.org/fulldisclosure/2026/Oct/56"
],
"discovery": "EXTERNAL"
},
"title": "[0day-rubbish] NUUO Crystal Server 3.19.0.85 unauthenticated upload.php PHP web-root write with fastcgi execution into server-side code execution as the web service account (9.8 Critical)",
"x_gcve": [
{
"recordType": "advisory",
"relationships": [],
"vulnId": "GCVE-1988-2026-0505",
"x_vulnarchive": {
"archiveUrl": "https://vuln.freearchive.org/archive/full-disclosure/2026/Oct/56",
"automated": true,
"contentSha256": "398d33967e157b5fddc8742b259ee4e769fe2a28ff200670d7b82d9ba4d1cd61",
"evidenceScore": 11,
"messageId": "",
"originalUrl": "https://seclists.org/fulldisclosure/2026/Oct/56",
"policy": "vulnarchive-1",
"sourceFormat": "text/html",
"sourcePublishedAt": "2026-10-27T16:39:30Z"
}
}
]
}
},
"cveMetadata": {
"assignerOrgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
"assignerShortName": "VULNARCHIVE",
"datePublished": "2026-11-02T07:30:04Z",
"dateUpdated": "2026-11-02T07:30:04Z",
"state": "PUBLISHED",
"vulnId": "GCVE-1988-2026-0505"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
How durations are calculated
Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.
Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.