Full Disclosure mailing list archives
[0day-rubbish] Abyss Web Server X1 2.16.22.0 first-run console credential takeover to configuration-write log-as-CGI command execution (7.5)
From: disclosure via Fulldisclosure <fulldisclosure () seclists org>
Date: Tue, 13 Oct 2026 02:20:48 +0000
0day Rubbish Research Team is publicly disclosing a vulnerability chain in Abyss Web Server X1 2.16.22.0, the free edition of the closed-source Abyss web server from Aprelium (Tunisia), a product family that has shipped for more than twenty years. The release artefacts identify 2.16.22.0 as the vendor's current official release. Between first start and the moment an administrator sets a console password, any client admitted by the shipped console access rule can register that administrator with no credential at all, and three ordinary configuration forms afterwards convert the configuration plane into an execution plane: the host access log becomes a shell script inside CGI scope, and any anonymous request to the log path executes it. Type and root cause. CWE-306, missing authentication for a critical function, is primary, and CWE-78, OS command injection, is co-equal rather than subordinate. CWE-284, CWE-73, CWE-362, CWE-352, CWE-328 and CWE-250 contribute and are reported separately without raising any metric. The two primary classifications are co-equal because removing either one alone removes the chain while leaving every other weakness discussed here intact. It is worth stating what this finding is not, because the technique is the interesting part: there is no memory corruption, no deserialisation gadget and no file-upload primitive anywhere in it. The write primitive is the product's own supported configuration form, and the command enters the written file through a quote-context escape in a request header field. A fix that only adds authentication to the console leaves the log-as-interpreter collision in place; a fix that only filters the header leaves the unauthenticated credential creation in place. Neither half is sufficient on its own. The console authenticates with HTTP Basic on its default port 9999, binds `bindip` `*` by default, and issues no session cookie anywhere - every endpoint answers without `Set-Cookie`, including the endpoint that creates the administrator. On first start the generated `abyss.conf` carries a `<console>` node with no `<login>` and no `<password>` element, and the startup terminal prints a message telling the operator to browse `http://127.0.0.1:9999` to continue with the console access configuration. While that state holds, `GET /console/` answers a distinctive `302` and the bootstrap chain leads to `GET /console/credentials` answering `200` with a creation form plus a `$$xxvxd` page token; a credential-free `POST` to that endpoint installs the administrator. On an instance that has never been configured the redirect first passes through `/console/language`; on an instance whose language is already chosen it goes straight to `/console/credentials`. Both shapes are unauthenticated throughout. After the install the same endpoint answers `401` with `WWW-Authenticate: Basic Realm="Abyss Web Server Console"`, so the attacker's own POST is what closes the window. The three forms that follow are a direct serialisation of the configuration tree - each HTML control's `NAME` is the XML key path it writes - so what they accept is exactly what the configuration can hold. - `POST /hosts/host@0/edit/scripting/paths/path@0/edit` takes `vpath=/` with `type=0` (CGI) and writes `<paths><path><vpath>/</vpath><type>0</type>...</path></paths>`. A `vpath` of `/` places the entire virtual path space of the host inside CGI scope, which includes files the server writes into the document root itself. There is no minimum-depth requirement and no warning about scope width. - `POST /hosts/host@0/edit/log` takes an arbitrary absolute filesystem path in its `file` control and a `format` of `.combined`, writing `<log><file>(absolute path inside the document root)</file><denied/> <format>.combined</format></log>`. No whitelist, no restriction to a log directory, no rejection of a destination inside the document root, and no canonicalisation. The control renders with a `Browse` selector that walks the target's directory tree from the filesystem root downwards, so the document root does not have to be guessed. - `POST /hosts/host@0/edit/scripting/interpreters/interpreter@0/edit` takes an arbitrary executable path with `interface=0`, `checkexists=yes` and `type=0`, and a second-step extensions sub-form then associates an arbitrary extension string. Submitting `/bin/sh` and `ext=log` writes `<interpreters><interpreter><file>/bin/sh</file>...<ext>log</ext> </interpreter></interpreters>`. `checkexists=yes` is satisfied because `/bin/sh` exists; nothing reasons about whether the extension names a content type rather than a destination type, whether the interpreter is a general-purpose shell, or whether the server itself writes files with that extension. `POST /` carrying `/restart=Restart` - the product's own restart button, with the main page showing "The configuration has changed. Press [Restart] to apply the modifications." and host status `Running(Updated)` - applies all three. The request-serving slave process bounces, and only the new generation writes into the newly configured log destination. The access log is now a shell script inside CGI scope. A request whose `User-Agent` is `zz"; (command); #` appends a `.combined` line in which that value lands inside the format's own double-quoted field, where the quote closes the field, the semicolon separates a statement and the trailing comment swallows the remainder. An anonymous `GET` on the log path then makes the product start the CGI child `/bin/sh (log file)`, and the shell reads the whole file line by line, executing the semicolon segment. Commands run as the identity of the server process. How confirmation works. The final trigger answers `500`, not `200`, and that is the appearance of success rather than failure: `/bin/sh` writes no CGI headers, so the product cannot construct a valid CGI response and answers with its standard 348-byte error page while the CGI log records `Broken pipe` for that invocation. Proof is server-side and three-channel rather than client-observed. The product's own CGI error log names the child it spawned directly, `CGI: [/bin/sh x.log ] URI: /x.log`, interleaved with the shell's own diagnostics `x.log: line N: 127.0.0.1: command not found` - those are the log's ordinary leading fields being treated as command words, and they do not stop execution of later lines, which is why a noisy log prefix is harmless. The diagnostics incremented from two lines to four as the log grew between fires, which is causality visible in the log: each fire re-reads the whole file and re-executes every line. A marker file written by the injected command contained `uid=uid=0(root) gid=0(root) groups=0(root)`, 43 bytes and root-owned; the doubled prefix is the injected command's own literal, not a product string, and the identity the server process ran under is `uid=0(root) gid=0(root) groups=0(root)`. Neither HTTP status in the last two steps indicates failure - the smuggling request only has to be logged, and it was answered `404` with a 336-byte body on a nonexistent path precisely so it would have no other effect on the target. What was deliberately not reproduced. The published advisory reproduces the identity string and the product's own log lines; it does not publish the per-run marker token, the credential pair used in verification or any hostname from the run, because all three are artefacts of one execution rather than facts about the product. No credential was leaked, seeded or reused: the pair was chosen by the test operator at that moment, it is not a product default, and this post reproduces no credential. No reverse shell, no callback listener, no persistence mechanism and no destructive payload was exercised. Scoring. One score is published: 7.5 High, `CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H`. That vector computes to 7.493264 raw - ISS 0.914816, Impact(S:U) 5.873119, Exploitability 1.620146 from weights AV 0.62, AC 0.44, PR 0.85, UI 0.85 - and is taken to 7.5 by the CVSS ceiling-to-tenth Roundup. No pre-existing numeric score was inherited: the sources behind this advisory carried no computed CVSS figure, so there was nothing to correct, and every number published here was computed from its vector. The Attack Vector is Adjacent, not Network, and the reason is an artefact rather than a judgement call. The console binds every interface by default and the shipped documentation presents that as the recommended setting, but its `ipcontrol` block permits loopback, all three RFC1918 IPv4 ranges (`10.0.0.1-10.255.255.254`, `172.16.0.1-172.31.255.254`, `192.168.0.1-192.168.255.254`) and IPv6 unique-local plus link-local, with no deny entries, and refuses public-internet sources. The check is evaluated on the connected socket peer and was measured not to be influenced by `X-Forwarded-For` or `X-Real-IP`. Those same address-range literals appear as byte literals inside the stripped binaries. A default installation is therefore not internet-reachable, and no published score assumes that it is. The adjacent position is genuinely wide in practice - the whole office LAN, the whole home LAN, every tenant on a shared management VLAN, every container on a bridge network, every instance on a cloud VPC subnet - but it is adjacent, and the headline vector says so. The Attack Complexity is High because success requires arriving before the administrator arms the console. The endpoint answers `401` from the moment a credential exists, and that was measured in every verification round. The window's field duration is not established and is not estimated anywhere in this research; the advisory invents no figure for it. Two conditional readings are published alongside the primary, each labelled as conditional and neither quoted as the headline. - Conditional A, the window left open indefinitely: `CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H` = 8.8 High (Exploitability 2.835255, base 8.708373). Applies where automation installs and never arms the console, where appliances are staged before deployment, or where containers and host images regenerate a pristine configuration on every restart, so the attacker polls an unauthenticated state rather than racing a person. - Conditional B, the console also exposed to a network: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H` = 9.8 Critical (Exploitability 3.887043, base 9.760161), and with the window contested `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H` = 8.1 High (Exploitability 2.221167, base 8.094286). Applies where an operator publishes port 9999 through NAT or a port forward, binds it on a publicly addressed host, or relays through a same-host reverse proxy whose forwarded traffic presents a permitted socket peer. That proxy shape passes `ipcontrol` without any header logic being consulted at all. Both are conditional on a deployment change we did not test. One reading was rejected. Scope Changed was considered seriously and refused: `CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H` computes to 8.4 (ISS 0.914816, Impact(S:C) 6.128026, Exploitability 1.620146, base 1.08 times 7.748172 = 8.368026) and is not published as the finding's score. Three reasons. Both listeners belong to one product and one process family - a CLI master and a `--slave --interactive` service process - so the console and the host plane are two doors into one authority, not two authorities, and applying configuration to the serving plane is the product's documented intended behaviour. The CGI child runs as the server process's own identity on the same host, in the same filesystem view, so no different security authority is crossed. And the operating-system consequences depend on how the deployment runs the server, which is established below as deployment-dependent; scoring Changed on the strength of a root deployment we could not prove to be the shipped default would be exactly the inflation the surrounding discipline exists to prevent. Authentication. The chain head requires no principal at all - only that no credential exists yet and that the source address passes `ipcontrol`. The configuration-write and restart forms require a console administrator, and that administrator is the credential the unauthenticated request just minted, so no credential owned by anybody else is consumed at any point. That is why `PR:N` is the correct metric. `UI:N` because no administrator action, no browser session and no induced click is required at any step. The gate was tested by behaviour rather than trusted from configuration: after the credential was installed an unauthenticated `GET /console/credentials` returned `401` with the Basic realm, observed in every round and reproduced by an independent manual request sequence. Two negative controls isolate causality in a chain with two independently necessary links. The execution control deleted the marker, re-sent the identical `User-Agent` payload to a different nonexistent path - the fourth of five log lines in the quoted run, byte-identical in its payload portion - and deliberately did not request the log path; after a wait long enough to cover the observed flush latency the marker did not exist. A request carrying shell metacharacters in a header therefore executes nothing by itself, no matter how many times it is logged. The follow-up step requested the log path again and the marker reappeared with byte-identical content, which is the determinism half: the file write is necessary but not sufficient, the CGI fire is necessary but not sufficient, and the pair is sufficient and repeatable. A third control excluded spoofing by construction: the configuration contents and the marker file were each read server-side, and the CGI error log independently corroborated both the child's command line and the shell's line-by-line execution, so no claim rests on a client-observed value alone. Adjacent claims that are explicitly not made. No authentication bypass exists outside the window: once a credential is present no unauthenticated route to a configuration form was found, the console answers `401` across its routes, and repeated attempts against the creation endpoint after arming also return `401`. No session attack is claimed, because with no cookie mechanism there is no session fixation, no token theft and no cookie replay; `$$xxvxd` is a per-server-run page token that every client can read from any form page, so it is not a session secret and predicting it buys nothing - it was parsed, not attacked. No password-reset surface exists to bypass: the product has no forgot-password, recovery-email or secret-question flow, and the shipped documentation's recovery for a lost console password is a filesystem operation. Password guessing was neither needed nor attempted, and the absence of observed rate limiting on the console's Basic authentication is reported as a weakness in its own right rather than folded into the chain. The window, and a caveat that is easy to get wrong. The window is re-enterable and is not once per machine. It re-opens whenever the credential nodes are removed, which is also the product's own documented recovery for a forgotten console password, and it re-opens on any redeployment that regenerates the configuration - a CLI redeployment from a pristine tree, an operator following that documentation, a host image rebuilt from a clean tree, or a container started from a fresh configuration image. Describing this as a one-shot race that ends forever after first setup is incorrect. What is one-shot is the transition per configuration state: the endpoint answers `401` from the moment a credential exists. Separately, the unarmed-versus-armed state is discoverable without any authentication at all. An unauthenticated `GET /console/` answers a distinctive `302` while the instance is unarmed and `401` once it is armed, so an attacker can survey a private network continuously for unarmed instances and race any administrator to them. That converts the race from luck into monitoring, and the advisory reports that discovery primitive to the vendor separately rather than treating it as a detail of the main chain. What is not established, and is not estimated, is how long the window stays open in a real deployment: the sources contain no field measurement of the interval between installation and arming. The shapes differ by orders of magnitude - an administrator who follows the startup message immediately closes it in seconds, while automation that installs and never arms, an appliance staged before deployment, a development instance left running or a container rebuilt from a pristine image on every restart may leave it open indefinitely, and in that last shape the attacker keeps arriving rather than racing anybody. Impact. Full administrative takeover by an unauthenticated client on the same private network, with a minted console credential that is structurally indistinguishable from the legitimate one - the same `root/console/login` node and the same MD5 digest storage - so the intended administrator who arrives second is locked out with a `401` and no alert distinguishes what happened from a typo. From there: - arbitrary operating-system command execution as the server process identity through the product's own CGI mechanism, with no payload upload required, because the access log is the payload carrier and it re-executes on every request to the log path until the log is rotated or truncated; - read access to everything that identity can read - all served content, every TLS private key reachable through the console's certificate management, and the host filesystem to that identity's limits, which at root means the entire host; - complete control of the configuration tree of every virtual host: scripting and interpreter bindings, logging, TLS material, access control, proxying, and the console's own listener settings, including the ability to widen `ipcontrol` or rebind the console port, which removes the defender's ability to re-narrow the surface remotely; - persistence in at least four independent ways: the minted credential survives restarts; the interpreter binding and CGI scope survive restarts and are invisible to a casual audit; the smuggled command stays in the log file and fires on every request to that path; and the executed command can install an operating-system startup mechanism with no further product interaction; - availability impact at will: hosts can be stopped, the service restarted repeatedly, and the arbitrary-path log write alone permits filling a filesystem as a standalone primitive independent of the execution chain. Two primitives are reported to the vendor separately and are given no score of their own, because scoring them here would double-count what the chain already carries: the arbitrary-path continuous append as the server user, for which log poisoning and disk filling need nothing else in the chain, and the console file browser's read-only whole-filesystem enumeration. Also reported separately, and folded into no metric, are the amplifiers: the default `ipcontrol` breadth combined with an all-interface bind and the documentation's "recommended" framing of that bind (CWE-284); configuration writes carrying no CSRF token with no session cookie (CWE-352); MD5 password retention (CWE-328); console Basic authentication with no observed rate limiting while `antihack`, the request-filtering subsystem that could have evaluated the payload, defaults to `enabled no`; and the absence of enforced privilege separation (CWE-250). Verification boundary, stated plainly. All dynamic work ran against a live instance of the product built from the official Linux release tree `abwsx1.tgz`, answering real HTTP traffic - not an emulation, not a protocol replica and not a reimplementation. Every request was answered by the vendor's own stripped binary, and every server-side artefact quoted was read off that instance's disk. Three rounds produced the `uid=0(root)` marker green: one manual request sequence driven end to end with command-line HTTP tooling, and two fully automated rounds of a standard-library driver, each exiting zero, the second with cleanup disabled so the scene could be inspected. The two automated rounds were line-for-line identical on the client side apart from the omitted cleanup segment and the per-server-run page token, which differs between runs by design. One further observation is reported because it is evidence rather than a footnote: a round in which the smuggling and fire steps ran before the new generation had taken over was served by the pre-restart process, wrote the payload into the previous log destination and produced no marker, which is what proves the log redirection is generation-scoped and why the shipped tool waits on a positive test of the redirect - a request with a distinguishing `User-Agent` appearing in the new file - rather than on the port coming back. The whole run, server and client together, sat inside a dedicated network namespace whose only interface was loopback, brought up before the server started; every request in every round was sent to `127.0.0.1`. A listener survey performed in the host's root namespace while the run was in progress returned zero hits on either product port, which is the isolation evidence - a `bindip` of `*` inside a namespace with no other interface cannot be reached from outside it. Consequently no step of the chain was driven from a separate network host, and the claim that an attacker sharing a private network can do this is argued from shipped artefacts - the all-interface bind and the `ipcontrol` allow ranges, whose literals also appear inside the binary images - rather than demonstrated between two machines. A two-host detonation would be the direct demonstration and was not performed, because running the product with its default all-interface bind on a reachable network was not acceptable for this work. Reproducing it from a remote position would strengthen the finding, not contradict it. This is a pure binary-audit case with no source code of any kind in the release tree. Two stripped native images were analysed: `abyssws-x64`, ELF 64-bit stripped, 5700872 bytes, BuildID `d7545bad2f47fb7b9f269ae98cb7baea1594fa12`, and `abyssws-x86`, ELF32 i386 stripped, 6174164 bytes, BuildID `81b83c50c27df109282654730c52539a7db37cc2`; OpenSSL 3.x, nghttp2, PCRE2 and zlib are statically embedded in a hand-written C HTTP stack. The administration console's HTML is generated from templates compiled into those binaries and the shipped `console.js` is a zero-byte placeholder, so the forms and their accepted values had to be recovered rather than read. The recovery method is behavioural: fetch each console page and read the emitted `FORM` control names, which are configuration key paths; index the shipped `lang/*.lng` UI semantic tables together with a 47324-line string dump of the x64 image; then submit each form and read back the resulting configuration file. That yields the interface contract and its effects, and it is the limit of the method - no disassembly-level symbol evidence is cited anywhere in the advisory, and none is needed for the claims made, because every claim is anchored to a form field, a configuration fragment or an observed process behaviour instead of a symbol. It also means the sink is composite and has no single function address to cite, which is normal for a configuration-plane defect in a stripped binary. The sink was found from the configuration side rather than from a crash or a disassembly: because control names are key paths, reading the generated default configuration maps everything the console can change, and the chain came from comparing two innocuous answers - a log form that accepts any absolute destination, and an interpreter form that accepts any executable and any extension without reasoning about whether the two could ever coincide. Not exercised, and not claimed. The x86 image in the same tree was not executed; it shares the release artefacts, the configuration model and the console templates and is characterised from them rather than from a detonation. The Windows build of the same version was not obtained for testing, the paid X2 edition was not tested, and earlier 2.x versions were not tested. Only this version was tested. The effect of enabling the `antihack` protection system on this payload was not measured, so it is listed as a hardening measure and never claimed as a tested mitigation. The chain's behaviour against a target with active log rotation, a log destination on a different filesystem, or a document root the server user cannot write was not measured. No privilege escalation beyond the server process identity was attempted or claimed - the product is a single-process C server model with no setuid helper chain observed - and no cross-host movement, container escape or sandbox escape is claimed. The parser memory-safety family (chunked transfer state machine, multipart ranges, `Expect` handling, HTTP/2 glue) was not fuzzed; it is named for the vendor as unexhausted surface and as a hardening target, not as a finding. The privilege ceiling, stated exactly. The achieved identity is arbitrary command execution as the identity of the server process, and that identity is deployment-dependent. `uid=0(root) gid=0(root) groups=0(root)` was observed on the verified instance, where the marker file was 43 bytes and root-owned. Root was reached because that instance was started with the product's own `--allow-root` CLI flag from a working copy of the release tree on a host where the process was launched by a privileged user. Root execution is not established as the shipped default on Linux, and no such claim is made. Separating the three levels of evidence: it is artefact-backed that `--allow-root` exists in the product's CLI family, that the process accepted it, and that the resulting CGI child ran with that identity; it is characterised but not artefact-proved that a dedicated low-privilege `abyss` account is intended for production deployments, because no shipped artefact - install script, init unit file, or a user-setting node in the generated default configuration - proves that is the default runtime identity; and it is a structural argument rather than a measurement that the default port-80 bind implies the process starts with root privileges or holds `CAP_NET_BIND_SERVICE`, since on Linux binding below 1024 requires one of the two, and whether privileges are then dropped, and to which identity, depends on how the deployment starts the server - a systemd unit with `User=`, an init script, a manual start, the vendor's autostart setup routine, or an `--allow-root` start. A stock deployment that simply runs the binary with enough privilege to bind port 80 and never drops is a realistic shape, not an exotic one. No CVSS metric was raised or lowered on account of any of this: either identity yields full compromise of the product, its configuration, its keys and its served content, and root additionally yields the host. Impact C/I/A are all High on that basis and the rating does not depend on resolving the question. Detection notes, including the reasons detection is weak by construction. Every request in the chain is legitimate documented product usage: a setup form, ordinary authenticated configuration submissions with the same content type and shape as any administrator session, the product's own restart button, a `404` with an unusual `User-Agent` among many `404`s, and an anonymous `500` on a document-root file. There is also no meaningful host-based anomaly, because the executed command is a child of the server process, its parent is the product's own CGI machinery, and no new binary, new port or new account is required. The signals that do exist, in order of value: `cgi.log` entries naming a non-script file, which is the server telling you a shell was invoked on a log file, together with the shell's own `line N: ... command not found` diagnostics, the highest-fidelity indicator available; `500` responses for paths ending in a data extension such as `.log`, `.txt`, `.tmp` or `.bak`, since the product answers `500` whenever a bound interpreter fails to produce CGI headers; configuration drift, namely a `<paths><path><vpath>/</vpath>` entry, an interpreter whose `<file>` is a shell, or a `<log><file>` pointing inside the document root, each of which should fail an audit on its own merits; an administrator credential nobody recognises, or a legitimate administrator receiving `401` on first visit to a freshly deployed instance; access-log lines whose `User-Agent` contains shell metacharacters - `";`, `#`, `$(`, redirections - which are rare in benign traffic and are the payload's signature; and repeated unauthenticated `GET /console/` probes from many hosts, which indicate window surveying. Status code alone is not a usable detector for the takeover itself, because a minted credential produces no failed login, no session creation and no distinguishable log entry. Mitigation summary, ordered so that the first item alone closes this chain. Refuse the colliding pair at configuration-commit time: cross-check every `<interpreters><interpreter><ext>` value against every host `<log><file>` extension and against the product's own known output extensions, and refuse the write with a specific error rather than committing it and letting the intersection execute - this single check removes the mechanism that turns the log into an executable path. Refuse to bind a general-purpose shell as an interpreter for a data extension, requiring an explicit separately confirmed override for `/bin/sh`, `/bin/bash`, `/bin/dash`, `cmd.exe`, `powershell` and equivalents, since a data extension bound to a shell has no legitimate purpose in a web server's own configuration. Do not let one console form write an arbitrary vpath-to-interpreter pair: validate the scripting subtree as a unit and require explicit acknowledgement for a CGI script-path scope of `/`, which is scope-wide by construction and should not be a value an ordinary form silently accepts. Constrain the log destination and the file browser: canonicalise console-supplied paths, resolve them against the server's own root, reject destinations inside the document root, inside any CGI script path or outside an allowed log directory, and restrict the console browser to the same allowed subtrees instead of enumerating from the filesystem root. Make console credential creation unreachable from the network by generating the initial credential locally at first start - a random password written into the configuration with root-only permissions and printed to the startup terminal, or a forced local or installer-bound setup - so the credential exists before the console listener can serve a creation form; if a network-visible creation step is kept, bind it to loopback or require a one-time setup token read from the local filesystem. Narrow the console's default exposure by binding `127.0.0.1`, treating non-loopback binding as an explicit opt-in with a warning, replacing the hardcoded RFC1918-wide and IPv6 ULA/link-local allow ranges with loopback-only plus per-range operator opt-in, and withdrawing the documentation's "recommended" framing of an all-interface console bind. Add a same-site CSRF token to every mutating console form. Enforce privilege separation by running the request-serving worker under a dedicated unprivileged account regardless of how the process was started, reserving root for the low-port bind or using `CAP_NET_BIND_SERVICE`, documenting the runtime identity so operators can audit it, and making `--allow-root` warn on use. Modernise credential handling as defence in depth with a memory-hard or bcrypt-class digest and with rate limiting and lockout on console Basic authentication. And add a regression test: commit a configuration with a `log`-extension log destination inside the document root, an interpreter binding `ext=log` to `/bin/sh` and a `<vpath>` of `/`, assert that the commit is rejected naming the collision, assert separately that requesting such a log path returns its content as `200` `text/plain` rather than `500` with a `CGI: [/bin/sh ...]` entry in `cgi.log`, and assert from a first-run configuration that the credential already exists before the console listener accepts its first connection. Note that fixing in the reverse order, starting with the log writer or with header filtering, would not break this chain. Until a patch exists operators can close it themselves: arm the console in the same automation step that installs the server and never redeploy from a pristine configuration without arming it; bind or firewall the console port to loopback or named management addresses and never publish it through NAT or a same-host reverse proxy without authentication in front; audit for the three collision signals named above; watch `cgi.log` for shells invoked on data files; run the server unprivileged without `--allow-root`, using a bind capability, a port redirect or a socket handed in by the init system on port 80; and re-arm from the local filesystem any deployment whose arming history is unknown, since a minted credential cannot be told apart from a legitimate one - which is also the recovery the product documents. The proof of concept is published in the repository at product/aprelium/abyss-web-server-x1-2.16.22.0/firstrun-console-takeover-conf-to-cgi-rce/exploit/aprelium_abyss_web_server_firstrun_console_takeover_conf_to_cgi_root_rce.py and uses the Python standard library only. Target, both ports, the credential to mint, CGI script-path scope, log format and destination, log URI, smuggling and control paths, interpreter, extension, command, marker path, every timeout and poll count are all command-line arguments with loopback or neutral placeholder defaults, and the tool writes a credential and changes the target's configuration rather than merely probing it. Beyond the default mode, which runs window probe, credential creation, arming-gate assertion, configuration write, restart, `User-Agent` smuggling, interpreter trigger, marker report, negative control and a determinism re-trigger, it offers `--probe` to check only whether the first-run window is open, changing nothing and exiting non-zero when the console is already armed; `--gate` to assert only that the credential endpoint refuses with `401` and a Basic realm once a credential exists; and `--control` to run only the negative control against an already configured instance. Safety rails retained from the verification work are a loopback-only target unless `--allow-remote` is given explicitly, a non-destructive marker command by default, no callback listener, no reverse shell, no persistence mechanism, and cleanup that removes only files the run itself created. Full technical analysis and a reproducible proof-of-concept: https://0day-rubbish.com/blog/aprelium-abyss-web-server-firstrun-console-takeover-conf-to-cgi-rce Project archive (ongoing disclosure series): https://github.com/Exploit-Garbage/0day-Rubbish Aprelium has been notified through the Standard Technical Support mailbox it publishes and documents for all users of both the free and the commercial edition of this product, with the same technical content and the same verification boundary as this post. The vendor publishes no RFC 9116 security.txt and no product security page, and its legal, privacy and support policy pages contain no vulnerability-reporting language at all, so there was no security-specific channel for us to use and no pre-publication form to submit to. The notification requests nothing in return and asks for no permission: this disclosure is unconditional, the research is already public, no embargo was offered or accepted, and nothing is being withheld pending a reply. No vulnerability identifier has been assigned to this finding yet. -- 0day Rubbish Research Team disclosure () 0day-rubbish com https://0day-rubbish.com _______________________________________________ Sent through the Full Disclosure mailing list https://nmap.org/mailman/listinfo/fulldisclosure Web Archives & RSS: https://seclists.org/fulldisclosure/
Current thread:
- [0day-rubbish] Abyss Web Server X1 2.16.22.0 first-run console credential takeover to configuration-write log-as-CGI command execution (7.5) disclosure via Fulldisclosure (Oct 15)
