[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fT8lMaockCBAR5woQF5XiU6obOW64NgRn52l0tWux9Xg":3},{"article":4,"iocs":39,"watch_terms":44,"solutions":45,"lesson":145},{"id":5,"title":6,"slug":7,"summary":8,"ai_summary":9,"brief":10,"url":11,"image_url":12,"published_at":13,"ingested_at":14,"relevance_score":15,"entities":16,"category_id":26,"category":27,"article_tags":31},"3179f2ef-fbd5-43d2-b0c5-0ea4464e8aca","Rapid7 Analysis: Unauthenticated File Upload to RCE in BeyondTrust Remote Support (CVE-2026-102764)","rapid7-analysis-unauthenticated-file-upload-to-rce-in-beyondtrust-remote-support-b461e0","Overview An origin-validation flaw in BeyondTrust Remote Support lets an unauthenticated attacker write files on the appliance and execute code as the site's operating-system user. The entry point is the public customer connection used to start a support session. No account credentials or user interaction are required. The flaw sits between the internal message router, brain, and an appliance-side customer worker, thin-scc. An attacker can put a reserved local router ID into a message's source address. The router forwards the message, and the worker accepts that address as proof that the request came from a local component. This gives the attacker access to an internal file-transfer service, including its native filesystem provider. We first confirmed the file write then used the primitive to upload a PHP file to the site's public directory. Requesting that file gave us code execution as the site user. A Metasploit module now implements the chain with PHP and Unix Command targets. We tested Remote Support 24.1.2, build 1524046, revision 4208-ddcf90715106aa0c0e19c103f8d2c412af4beff3. The test appliance had public issue-menu session starts and thin chat enabled (default). Other affected releases and fixed builds have not been established. The VM's base appliance version of Secure Remote Access, 7.1.0, is separate from the Remote Support application version. Impact The attacker controls both the contents of an uploaded file and its destination path, subject to the worker's filesystem permissions. The same origin forgery also reaches the file-send operation. We used it to retrieve \u002Fproc\u002Fself\u002Fenviron to determine the base installation path of the Remote Support before dropping the payload On the tested appliance, the native file service could write into the PHP document root. The Metasploit module returns code execution in the context of msfuser, UID 1000, which establishes unauthenticated remote code execution as the site user. Technical Analysis We will demonstrate how an attacker can start an anonymous session and then forge a message that the appliance will accept as local. Then outline how we can reach the native file transfer service, choose the uploaded file’s contents and destination and turn the file write into PHP execution. Addresses use Ghidra image base 0x100000. Subtract that value for the raw ELF virtual address. Function names beginning with FUN_ are Ghidra labels; descriptive names are taken from diagnostic strings where available. Starting an anonymous customer session We begin by obtaining two customer keys from POST \u002Fapi\u002Fstart_session. These are fresh public guest sessions. The keys let the client use the customer transport, but do not identify a logged-in account. Each guest connects through the \u002Fnw WebSocket endpoint, using either the native or thin customer protocol. source\u002Fapp\u002Fwrite_app_chooser_config: echo \"ingredi support desk customer:|$BG_app_root\u002Fdata\u002Ftmp\u002Fbrain.sock\" # [A1] echo \"ingredi support desk customer thin:$BG_app_root\u002Fapp\u002Fthin-scc-wrapper\" # [A2] app\u002Fwrite_app_chooser_config shows where the two customer protocols go. The native protocol reaches the brain socket at [A1]; the thin protocol launches a wrapper at [A2]. source\u002Fapp\u002Fthin-scc-wrapper: # ... omitted: protocol parsing and the alternative debug-site branch ... quoted=$(export PHPRC=\"$BG_app_root\u002Fconfig\u002Fphp-cli.ini\"; echo $gskey | $ingrediRoot\u002Fapp\u002Fdbquote) if [[ $(echo \"SELECT COUNT(1) FROM gw_sessions WHERE session_key = $quoted AND session_type = 'sdcust' AND (expiration IS NULL OR expiration>NOW())\" | $db) != \"1\" ]]; then # [A3] echo \"1 failure\" >&0 exit 0 fi # ... omitted: reconnect throttling, customer-name setup, and settings generation ... app\u002Fthin-scc-wrapper checks that the thin customer's key belongs to an unexpired sdcust session at [A3]. A key obtained through the public session-start endpoint passes this check. app\u002Fthin-scc-wrapper, after the session check: echo \"0 success\" >&0 workDir=`handleThinMint \"$thinMint\"` || exit if [[ ! -f \"$workDir\u002Fthin-scc\" ]]; then pushd \"$workDir\" cp $ingrediRoot\u002Fapp\u002Fthin-scc . # ... omitted: license, image, and settings.ini setup ... else pushd \"$workDir\" fi export BRAIN_MELD_SOCKET_PATH=$ingrediRoot\u002Fdata\u002Ftmp\u002Fbrain.sock # ... omitted: server-side environment initialization ... .\u002Fthin-scc # [A4] Starts the worker inside the appliance environment. exitStatus=$? The same wrapper then prepares a working directory and starts thin-scc at [A4]. This process runs on the appliance, even though it is handling an anonymous customer's thin session. The native connection gives us a client for the internal message-routing system, referred to here as MRS. The thin connection gives us an appliance-side worker to address. Neither step requires a representative to accept the session. A network message can claim to be local MRS addresses have three parts: node:router:handler. Each message carries source and destination addresses in a 36-byte header made up of nine little-endian 32-bit fields. Header offset Field Value used by the exploit 0x04 Source node The customer's normal node 0x08 Source router Changed to the reserved value 1 0x0c Destination node The worker's node 0x10 Destination router The worker's router 0x14 Destination handler Main dispatcher, 2048, on the tested build 0x24 Payload Service name, newline, then packed fields app\u002Fbrain — ingress check: if (*(int *)((long)param_2 + 0xc) == *(int *)(param_1 + 0x10)) { *(uint *)(param_2 + 4) = (uint)*(byte *)(param_2 + 4); if ((*(int *)(param_2 + 1) == *(int *)((long)param_1 + 0x84)) && (param_3 != (ulong **)0x0)) { \u002F\u002F [B1] Rejects a network claim of this router's actual ID. if (lRam0000000001ec8610 != 0) { func_0x0168de00( 0x15, 3, \"dropping spoofed message that came in from the network but has a routerID of our own\\n\" ); } } else if (*(int *)((long)param_1 + 0x84) == *(int *)(param_2 + 2)) { \u002F\u002F ... omitted: delivery to a handler on this router ... } else { \u002F\u002F ... omitted: TTL checks, route lookup, and forwarding branches ... } } app\u002Fbrain has an anti-spoofing check in FUN_01566ae0, identified by its handleMessageFromOtherRouter diagnostic. At [B1], it rejects a network packet whose source router matches the receiving router's actual ID. Here, param_1 is the receiving router, param_2 is the packet, and param_3 is the incoming connection. Because param_2 points to eight-byte elements, *(int *)(param_2 + 1) reads the source router at byte offset 0x08. That leaves a gap: the check compares against the actual router ID, while downstream code also recognises a reserved local alias. On this appliance, the alias is 1. \u002F\u002F ... omitted: destination route resolution, loop checks, and diagnostic logging ... FUN_0155ec40( ppuStack_1300, ppuVar6, param_2, \u002F\u002F [B2] Forwards the packet carrying the caller-supplied source router. (int *)((long)param_2 + 0x24) ); The forwarding path in the same app\u002Fbrain function passes the packet onward at [B2]. It does not remove the source claim that matters to the worker's local-origin check. app\u002Fbrain — local-address predicate: bool FUN_0155ad40(long param_1, int *param_2) { bool bVar1; bVar1 = false; if (*param_2 == *(int *)(param_1 + 0x80)) { \u002F\u002F [C1] Address node must match the local node. bVar1 = true; if (param_2[1] != 1) { \u002F\u002F [C2] Router 1 returns true without the actual-ID comparison. return *(int *)(param_1 + 0x84) == param_2[1]; } } return bVar1; } The local-address predicate makes the mismatch clear. In app\u002Fbrain, FUN_0155ad40 first checks the node at [C1]. If that matches, router 1 is enough to return true at [C2]; the comparison with the actual local router is skipped. The rule is effectively node AND (router == 1 OR router == actual local router). An address supplied by a network client is being used to make that decision. app\u002Fthin-scc — message-origin wrapper: UNRECOVERED_JUMPTABLE = *(code **)(**(long **)(*param_1 + 8) + 0x50); (*UNRECOVERED_JUMPTABLE)( *(long **)(*param_1 + 8), param_1 + 0xb, \u002F\u002F [C3] Uses the dispatched message's sender address at byte offset 0x58. UNRECOVERED_JUMPTABLE ); The corresponding message wrapper in app\u002Fthin-scc, passes the message's sender address into its router object's local-address predicate at [C3]. Ghidra did not recover a reliable prototype for this indirect call; the caller consumes its result as a boolean. We checked this behaviour with the same file-service request under two origins. The ordinary customer source produced no file-service connection during a two-second observation. Changing the source router to 1 caused the worker to accept it. Reaching the native file-transfer service In app\u002Fthin-scc, FUN_0047a850 dispatches file-transfer messages. app\u002Fthin-scc — file-transfer dispatcher: cVar3 = func_0x0068e930(param_2); \u002F\u002F [D1] Locality is derived from the message sender address. if (cVar3 == '\\0') { if (lRam00000000009bade8 != 0) { puVar5 = (undefined8 *)func_0x0068efe0(param_2); func_0x00309380(0x66, 3, \"DISCARDING NONLOCAL MESSAGE: %s\\n\", *puVar5); } } else { func_0x0046d370(aplStack_68, \"ftsName\"); lVar7 = param_2 + 0x28; lVar1 = param_2 + 0x30; lVar6 = func_0x002a1970(lVar7, aplStack_68); if (lVar1 == lVar6) { puVar5 = (undefined8 *)0x9c3700; } else { puVar5 = (undefined8 *)(lVar6 + 0x40); } if ((puVar5[1] == *(long *)(param_1 + 0x2a8)) && ((puVar5[1] == 0 || (iVar4 = func_0x001804b0(*puVar5, *(undefined8 *)(param_1 + 0x2a0)), iVar4 == 0)))) { \u002F\u002F [D2] Matches the requested filesystem service name. \u002F\u002F ... omitted: string cleanup, optional reply check, and action dispatch shown below ... } \u002F\u002F ... omitted: mismatched-name cleanup ... } It calls the origin wrapper at [D1] and discards a message if that wrapper says the sender is nonlocal. Once that check passes, [D2] matches ftsName against the service instance's name. The same function only validates responseAddress when the field is nonempty. The exploit omits it, taking the branch at [D3]. app\u002Fthin-scc, reply-address handling in FUN_0047a850: func_0x0046d370(auStack_a8, \"responseAddress\"); lVar6 = func_0x002a1970(lVar7, auStack_a8); if (lVar6 == lVar1) { lVar6 = 0x9c3700; } else { lVar6 = lVar6 + 0x40; } if (*(long *)(lVar6 + 8) == 0) { \u002F\u002F [D3] No reply-address validation when the field is empty. func_0x001edfe0(auStack_a8); } else { func_0x0046d370(auStack_88, \"responseAddress\"); lVar8 = func_0x002a1970(lVar7, auStack_88); lVar6 = lVar8 + 0x40; if (lVar8 == lVar1) { lVar6 = 0x9c3700; } func_0x006f2e80(auStack_b4, lVar6); func_0x0046d370(aplStack_68, \"processMessage\"); cVar3 = func_0x0068e950(param_2, aplStack_68, auStack_b4); \u002F\u002F [D4] \u002F\u002F ... omitted: temporary string destruction ... if (cVar3 == '\\0') goto LAB_0047a8be; } The validator called at [D4] compares the reply address against the message's sender address. It does not independently establish where that sender came from, so making this field mandatory would not fix the origin forgery. app\u002Fthin-scc, action handling in FUN_0047a850: \u002F\u002F ... omitted: action extraction and unrelated action cases ... iVar4 = func_0x0046d240(uVar9, uVar2, \"DownloadAnonymousFiles\"); if (iVar4 == 0) { uVar9 = FUN_00475820(param_1, param_2); \u002F\u002F [D5] Receives a file into the selected provider. goto LAB_0047a8c0; } \u002F\u002F ... omitted: DownloadFiles case ... iVar4 = func_0x0046d240(uVar9, uVar2, \"UploadFiles\"); if (iVar4 == 0) { uVar9 = FUN_00477be0(param_1, param_2); \u002F\u002F [D6] Sends a file from the selected provider. goto LAB_0047a8c0; } The dispatcher then exposes DownloadAnonymousFiles at [D5] and UploadFiles at [D6]. The action names describe the worker's side of the transfer. DownloadAnonymousFiles receives bytes onto the appliance; UploadFiles sends a file out. To upload our file, we ask the worker to download it from an MRS listener we control. The important thing to note here is that the worker has more than one filesystem provider. The session service delegates filesystem work to the browser. A successful transfer through that service does not prove a file was written on the appliance. The local service uses native file objects instead. app\u002Fthin-scc — native service construction: uVar3 = func_0x00770620(0x310); plVar4 = (long *)func_0x00770620(8); func_0x003454a0(plVar4); \u002F\u002F [E1] Constructs the native filesystem provider. pplStack_c0 = (long **)0x0; plStack_c8 = plVar4; pplStack_c0 = (long **)func_0x00770620(0x18); plVar1 = _DAT_007d4de0; pplStack_c0[2] = plVar4; pplStack_c0[1] = plVar1; *pplStack_c0 = (long *)&UNK_0098cee0; plStack_60 = (long *)0x5; uStack_58 = (long *)CONCAT26(uStack_58._6_2_, 0x6c61636f6c); \u002F\u002F [E2] ASCII \"local\". plStack_68 = &uStack_58; FUN_00472550(uVar3, &plStack_68, param_3, &plStack_c8); \u002F\u002F [E3] Service receives name and provider. param_1[0x9d] = uVar3; \u002F\u002F ... omitted: string\u002Fprovider reference cleanup and other ServerContext services ... The ServerContext constructor in app\u002Fthin-scc, FUN_002ae620, creates that native provider at [E1]. It builds the name local at [E2] and passes both to the file-transfer service constructor at [E3]. The exploit therefore uses ftsName=local. The native file factory analysis follows this provider to the native file and filesystem-info constructors. Choosing the file contents and destination app\u002Fthin-scc, FUN_00475820 , handles DownloadAnonymousFiles. app\u002Fthin-scc — anonymous-download request: \u002F\u002F ... omitted: overwrite\u002Fresume options and transfer metadata setup ... func_0x0046d370(aplStack_128, \"downloadToken\"); lVar8 = func_0x002a1970(lVar12, aplStack_128); lVar6 = lVar8 + 0x40; if (lVar8 == param_2) { lVar6 = 0x9c3700; } func_0x001edf30(&puStack_68, lVar6); \u002F\u002F [F1] Caller-supplied transfer token. func_0x0046d370(aplStack_148, \"serverAddress\"); lVar8 = func_0x002a1970(lVar12, aplStack_148); lVar6 = lVar8 + 0x40; if (lVar8 == param_2) { lVar6 = 0x9c3700; } func_0x006f2e80(auStack_1e4, lVar6); \u002F\u002F [F2] Caller-supplied MRS file-server address. func_0x0046d370(aplStack_168, \"path\"); lVar8 = func_0x002a1970(lVar12, aplStack_168); lVar6 = lVar8 + 0x40; if (lVar8 == param_2) { lVar6 = 0x9c3700; } func_0x001edf30(aplStack_88, lVar6); \u002F\u002F [F3] Caller-supplied destination path. uVar13 = (ulong)(iVar4 != 0); iStack_1e8 = func_0x004a21c0( param_1, aplStack_88, auStack_1e4, &puStack_68, auStack_198, iVar5 == 0 ); \u002F\u002F [F4] Starts the download using the supplied path, server, and token. \u002F\u002F ... omitted: temporary destruction and transfer-response construction ... The function reads the request's transfer token at [F1], MRS server address at [F2], and destination path at [F3]. All three reach the download routine at [F4]. In this excerpt, lVar12 points to the request's field map. param_2 has already been advanced to its map-end sentinel; 0x9c3700 represents an absent value. app\u002Fthin-scc — MRS download connection: (**(code **)(**(long **)(param_1 + 8) + 0x18))( &lStack_d8, *(long **)(param_1 + 8), param_3, 10000 ); \u002F\u002F [G1] Connects to the serverAddress supplied to this routine as param_3. if ((lStack_d8 == 0) || ((*(char *)(lStack_d8 + 0x30) != '\\0' && (*(char *)(lStack_d8 + 0x31) != '\\0')))) { uVar4 = 0; } else { \u002F\u002F ... omitted: request-map initialization ... func_0x00490020(&plStack_68, \"request\"); lVar5 = func_0x00202790(auStack_b8, &plStack_68); func_0x001f5f10( lVar5, 0, *(undefined8 *)(lVar5 + 8), \"get anonymous file\", 0x12 ); \u002F\u002F [G2] \u002F\u002F ... omitted: temporary key-string destruction ... func_0x00490020(&plStack_68, \"token\"); uVar6 = func_0x00202790(auStack_b8, &plStack_68); func_0x001ee710(uVar6, param_4); \u002F\u002F [G3] Copies the caller's downloadToken. \u002F\u002F ... omitted: info field, serialization, send, and receive-thread setup ... } The download routine at 0x004a21c0 opens an MRS stream to our address at [G1]. It sends get anonymous file at [G2] with our token at [G3]. The token can be a 24 character random string because we control the listener. \u002F\u002F ... omitted: stream setup, relay metadata, and reference management ... puVar7 = (undefined8 *)func_0x00770620(0x450); \u002F\u002F ... omitted: temporary setup and resume calculation ... func_0x004a0700(puVar7, param_1, param_5, bVar8, param_7, param_8); puVar7[0x81] = lStack_d8; puVar7[0x82] = lVar5; *puVar7 = &UNK_0099d3f8; \u002F\u002F ... omitted: relay address and transfer bookkeeping ... func_0x001ee710(puVar7 + 0x13, param_2); \u002F\u002F [G4] Retains the requested destination path. \u002F\u002F ... omitted: transfer registration and reference management ... func_0x002fcae0(puVar7, 0x400000); \u002F\u002F [G5] Starts the receive-files thread. The same app\u002Fthin-scc routine puts the destination path into a receive-files thread at [G4] and starts it at [G5]. app\u002Fthin-scc — receive-files loop: \u002F\u002F ... omitted: \"want file?\" command match ... func_0x00490020(ppplVar20, \"dest_filename\"); uVar12 = func_0x00202790(plStack_888, ppplVar20); puStack_8b8 = &uStack_418; func_0x001edf30(puStack_8b8, uVar12); \u002F\u002F [H1] Copies the destination from transfer metadata. \u002F\u002F ... omitted: provider filesystem-info query and reference cleanup ... bStack_931 = *(byte *)(plStack_848 + 0x1c); pplStack_8d8 = (long **)(((ulong)pplStack_8d8 & 0xffffffffffffff00 | (ulong)bStack_931) ^ 1); puStack_900 = auStack_3f8; if (bStack_931 == 1) { func_0x001edf30(puStack_900, puStack_8b8); } else { func_0x001fabe0(puStack_900, puStack_8b8, \".n$t\"); \u002F\u002F [H2] Temporary receive path. } The receive loop at 0x001d6430 consumes the file metadata we send over that stream. It copies dest_filename at [H1]. Depending on the provider's file information, it receives into that path or a temporary path ending in .n$t at [H2]. \u002F\u002F ... omitted: \"begin file\" handling and provider file-object creation ... func_0x00321f50(plStack_828, puStack_928); plVar11 = plStack_828; pcVar19 = *(code **)(*plStack_828 + 0xc0); func_0x00490020(ppplVar20, \"a+b\"); cVar5 = (*pcVar19)(plVar11, puStack_900, ppplVar20, lStack_908); \u002F\u002F [H3] Opens the receive file. \u002F\u002F ... omitted: open-failure handling, positioning, and transfer-loop branches ... iVar7 = (**(code **)(*plVar11 + 0x20))(plVar11, plVar1, pplVar21, pplVar25, 1); if (0 \u002F; the collection details below explain how these reach the site log directory. The installation path varies between appliances. Requests to an uploaded PHP endpoint The command-only Metasploit run created two public customer sessions, invoked its uploaded PHP endpoint, and then checked that the endpoint had gone. The relevant access-log lines are: 192.168.1.68 [28\u002FOct\u002F2026:21:12:23 +0000] \"POST \u002Fapi\u002Fstart_session HTTP\u002F1.1\" [0s] [Stat:200] [Size:1481] [Ref:-] [UA:Mozilla\u002F5.0 (Macintosh; Intel Mac OS X 14.7; rv:133.0) Gecko\u002F20100101 Firefox\u002F133.0] 192.168.1.68 [28\u002FOct\u002F2026:21:12:23 +0000] \"POST \u002Fapi\u002Fstart_session HTTP\u002F1.1\" [0s] [Stat:200] [Size:1481] [Ref:-] [UA:Mozilla\u002F5.0 (Macintosh; Intel Mac OS X 14.7; rv:133.0) Gecko\u002F20100101 Firefox\u002F133.0] 192.168.1.68 [28\u002FOct\u002F2026:21:12:31 +0000] \"POST \u002Fqdgusthrmlirprls HTTP\u002F1.1\" [0s] [Stat:200] [Size:0] [Ref:-] [UA:Mozilla\u002F5.0 (Macintosh; Intel Mac OS X 14.7; rv:133.0) Gecko\u002F20100101 Firefox\u002F133.0] 192.168.1.68 [28\u002FOct\u002F2026:21:12:38 +0000] \"POST \u002Fqdgusthrmlirprls HTTP\u002F1.1\" [0s] [Stat:404] [Size:18] [Ref:-] [UA:Mozilla\u002F5.0 (Macintosh; Intel Mac OS X 14.7; rv:133.0) Gecko\u002F20100101 Firefox\u002F133.0] 192.168.1.68 [28\u002FOct\u002F2026:21:12:43 +0000] \"GET \u002Fqdgusthrmlirprls HTTP\u002F1.1\" [0s] [Stat:404] [Size:18] [Ref:-] [UA:Python-urllib\u002F3.9] Here, \u002Fqdgusthrmlirprls mapped to qdgusthrmlirprls.php in the public directory. The first POST ran the command and returned 200. The later POST was the module's cleanup request; the GET independently checked the endpoint. Both received 404 after the file had deleted itself. This gives defenders a sequence to investigate: public session creation followed by a request to a new, unfamiliar endpoint, potentially followed by 404s for the same path. Session creation alone is normal Remote Support activity. A 200 followed by a 404 is also insufficient by itself; correlate the requests with file changes, session records, and any unusual outbound connections. The URL may not contain .php The earlier PHP canary produced these lines: 192.168.1.68 [28\u002FOct\u002F2026:20:50:07 +0000] \"GET \u002Fmsfcanary03d80193f0880b.php HTTP\u002F1.1\" [0s] [Stat:404] [Size:18] [Ref:-] [UA:Python-urllib\u002F3.9] 192.168.1.68 [28\u002FOct\u002F2026:20:50:35 +0000] \"GET \u002Fmsfcanary03d80193f0880b HTTP\u002F1.1\" [0s] [Stat:200] [Size:368] [Ref:-] [UA:Python-urllib\u002F3.9] 192.168.1.68 [28\u002FOct\u002F2026:20:50:35 +0000] \"GET \u002Fmsfcanary03d80193f0880b HTTP\u002F1.1\" [0s] [Stat:404] [Size:18] [Ref:-] [UA:Python-urllib\u002F3.9] The direct .php request failed, while the extensionless request executed the same file. The final 404 followed self-deletion. Detection that only searches for requests ending in .php would miss the successful request in this example. The native transfer itself travels inside the customer MRS connection. These access-log entries show the HTTP trigger, not the file contents or the forged router ID. The captured HTTP log does not expose the inner MRS messages, so it cannot establish the origin bypass on its own. Files and connections Look for unexpected PHP files under the site's public directory and receive files ending in .php.n$t. An interrupted transfer may leave the temporary file even if the final URL returns 404. The .n$t suffix also belongs to the normal transfer implementation, so its presence needs context. Native file-transfer traces thin-scc.log and thin-scc-errors.log contain more direct evidence of the file-service requests. For example, this line from thin-scc-errors.log names the action, native provider, and intended PHP destination. Only ckey and downloadToken have been redacted: 20261029 17:01:33 999999 thin-scc 11801:proc{LocalFileTransfer}(t11832) FT:WARNING>DISCARDING NONLOCAL MESSAGE: LocalFileTransfer:action=DownloadAnonymousFiles;ckey= ;downloadToken= ;enableResume=0;ftsName=local;path=\u002Fhome\u002Fmsfuser\u002Fingredi-24.1.2\u002Fapp\u002Fwww\u002Fpublic\u002Fenvresolve28f788af6a1c39bf.php;serverAddress=1:1022182175:9000;wantOverwritePrompts=0 This was our negative control: the request used the customer's ordinary source address and was rejected. It is evidence of an attempted invocation, not a successful upload. The forged local source passes the check that produces this warning, so a successful exploit need not leave a DISCARDING NONLOCAL MESSAGE entry. The successful PHP module run left these lines in thin-scc.log: 20261029 17:06:37 319 thin-scc 14419:proc{LocalFileTransfer}(t14486) MRS_STREAM:INFO>createStream: connecting to address 1:1129726787:1031 20261029 17:06:37 2 thin-scc 14419:proc{LocalFileTransfer}(t14486) MRS_CL:ERROR>nodeID not found in node list even after renumerating: 0 20261029 17:06:37 0 thin-scc 14419:proc{LocalFileTransfer}(t14486) MSG_DSP:ERROR>failed to create a mailbox for sending to: 0:0:0 Our requests omit responseAddress. The handler starts the transfer, then tries to send its action response to an empty address. That produces the 0:0:0 error without preventing the file write. The payload callback independently confirms execution at 17:06:37. Look for this combination in the proc{LocalFileTransfer} context, especially around unfamiliar PHP requests or outbound connections. The 1:1129726787:1031 value identifies an internal MRS mailbox; use the brain connection records below for the client IP. These entries do not identify the written filename or prove execution on their own. The empty-address errors also depend on how the exploit request is constructed; another implementation may leave different traces. Brain connection records brain.log helps link the activity to a client address. These two entries preceded the same module run: 20261029 17:06:30 0 brain 6339:lstner(t06500) BRN_LSTN:INFO>received fd: 47 app name: 'ingredi support desk customer' and remoteIPAddress: '192.168.1.68' and localIPAddress: '192.168.1.98' and isLoopback '0' 20261029 17:06:30 0 brain 6339:lstner(t06500) BRN_LSTN:INFO>received fd: 49 app name: 'ingredi support desk customer' and remoteIPAddress: '192.168.1.68' and localIPAddress: '192.168.1.98' and isLoopback '1' These are useful for a timeline, but customer connections are normal activity. We found no UploadFiles or DownloadAnonymousFiles action records in the captured brain logs. Generic EOF, connection-reset, and conference errors also appeared; they are too broad to treat as exploit indicators by themselves. Where to collect the native logs The aggregated logs are under $BG_app_root\u002Flog\u002F. While a worker is running, its own logs are under $BG_app_root\u002Fdata\u002Ftmp\u002Fsdcust_thin_client\u002F \u002F. The wrapper appends those logs to the site's log directory when the worker exits, then clears the per-worker copies. Collect both locations when investigating a live session. The captured brain.ini and thin-scc.ini both had the debug output disabled with enabled=0. No debug log was captured, and we did not change the logging configuration. Brain's debug configuration also limits MRS_* categories to Info, so enabling its debug output alone would not guarantee detailed message payloads. Names such as UploadFileStarted and DownloadFileEnded occur in the native protocol's callback code, but we did not find them in the collected logs. They should not be presented as default audit events","A critical origin-validation vulnerability in BeyondTrust Remote Support allows unauthenticated attackers to upload arbitrary files and execute code as the site's OS user. The flaw, identified as CVE-2026-102764, is triggered through the public customer connection used to initiate support sessions, requiring no credentials or user interaction. Attackers can exploit this to gain remote code execution by uploading a malicious PHP file to the web server's public directory.","Unauthenticated RCE in BeyondTrust Remote Support via file upload flaw.","https:\u002F\u002Fwww.rapid7.com\u002Fblog\u002Fpost\u002Fra-rapid7-analysis-unauthenticated-file-upload-to-rce-in-beyondtrust-remote-support-cve-2026-102764","https:\u002F\u002Fimages.contentstack.io\u002Fv3\u002Fassets\u002Fblte4f029e766e6b253\u002Fbltcbf09ec3af35e28e\u002F6a15c63f08221ed5144dbf77\u002Fwebinar-rapid7-logo.jpg","2026-10-30T16:01:53+00:00","2026-10-31T10:00:34.283818+00:00",9,[17,20,23],{"name":18,"type":19},"Remote Support","product",{"name":21,"type":22},"BeyondTrust","vendor",{"name":24,"type":25},"PHP","technology","80544778-fabb-4dcd-aa35-17492e5dcf4f",{"id":26,"icon":28,"name":29,"slug":30},null,"Vulnerabilities","vulnerabilities",[32,37],{"category":33},{"id":34,"icon":28,"name":35,"slug":36},"26b0b636-0e31-4db1-bffb-61bdf9f20a58","Supply Chain","supply-chain",{"category":38},{"id":26,"icon":28,"name":29,"slug":30},[40],{"type":41,"value":42,"context":43},"cve","CVE-2026-102764","Unauthenticated file upload to RCE vulnerability in BeyondTrust Remote Support",[21,18],{"controls":46,"vendors":133},[47,75,104],{"slug":48,"name":49,"problem":50,"products":51},"patch-management","Automated patch management","Known holes get closed in days, not months.",[52,60,68],{"vendor_slug":53,"vendor_name":54,"vendor_logo":55,"product_slug":56,"product_name":57,"url":58,"description":59},"ninjaone","NinjaOne","\u002Fvendor-logos\u002Fninjaone.png","ninjaone-patch-management","NinjaOne Patch Management","https:\u002F\u002Fwww.ninjaone.com\u002Fpatch-management\u002F","Automated patching of operating systems and third-party applications from the cloud.",{"vendor_slug":61,"vendor_name":62,"vendor_logo":63,"product_slug":64,"product_name":65,"url":66,"description":67},"manageengine","ManageEngine","\u002Fvendor-logos\u002Fmanageengine.png","patch-manager-plus","ManageEngine Patch Manager Plus","https:\u002F\u002Fwww.manageengine.com\u002Fpatch-management\u002F","Patch management for operating systems and third-party applications, on premises or in the cloud.",{"vendor_slug":69,"vendor_name":70,"vendor_logo":71,"product_slug":48,"product_name":72,"url":73,"description":74},"qualys","Qualys","\u002Fvendor-logos\u002Fqualys.png","Qualys Patch Management","https:\u002F\u002Fwww.qualys.com\u002Fapps\u002Fpatch-management\u002F","Patch deployment for operating systems and third-party applications, linked to vulnerability findings.",{"slug":76,"name":77,"problem":78,"products":79},"endpoint-detection-response","Endpoint detection and response (EDR and XDR)","Malware and hands-on-keyboard attackers on laptops and servers get spotted and stopped.",[80,88,96],{"vendor_slug":81,"vendor_name":82,"vendor_logo":83,"product_slug":84,"product_name":85,"url":86,"description":87},"eset","ESET","\u002Fvendor-logos\u002Feset.png","eset-protect","ESET PROTECT","https:\u002F\u002Fwww.eset.com\u002Fint\u002Fbusiness\u002Fsolutions\u002Fendpoint-protection\u002F","Endpoint protection platform with detection and response add-ons.",{"vendor_slug":89,"vendor_name":90,"vendor_logo":91,"product_slug":92,"product_name":93,"url":94,"description":95},"withsecure","WithSecure","\u002Fvendor-logos\u002Fwithsecure.png","elements-xdr","WithSecure Elements XDR","https:\u002F\u002Fwww.withsecure.com\u002Fen\u002Ffor-business\u002Fplatform\u002Fxdr\u002F","Endpoint detection and response with extended detection, on the Elements cloud platform.",{"vendor_slug":97,"vendor_name":98,"vendor_logo":99,"product_slug":100,"product_name":101,"url":102,"description":103},"elastic","Elastic","\u002Fvendor-logos\u002Felastic.png","elastic-security","Elastic Security","https:\u002F\u002Fwww.elastic.co\u002Fsecurity","SIEM and endpoint security built on Elasticsearch, with open detection rules.",{"slug":105,"name":106,"problem":107,"products":108},"network-segmentation","Network segmentation and microsegmentation","One compromised machine can no longer reach everything else.",[109,117,125],{"vendor_slug":110,"vendor_name":111,"vendor_logo":112,"product_slug":113,"product_name":114,"url":115,"description":116},"threatlocker","ThreatLocker","\u002Fvendor-logos\u002Fthreatlocker.png","zero-trust-endpoint-firewall","Zero Trust Endpoint Firewall","https:\u002F\u002Fwww.threatlocker.com\u002Fcapabilities\u002Fzero-trust-endpoint-firewall","Centrally managed host firewall with device-level and dynamic access rules, to limit which machines can talk to each other.",{"vendor_slug":118,"vendor_name":119,"vendor_logo":120,"product_slug":121,"product_name":122,"url":123,"description":124},"illumio","Illumio","\u002Fvendor-logos\u002Fillumio.png","illumio-segmentation","Illumio Segmentation","https:\u002F\u002Fwww.illumio.com\u002Fillumio-segmentation","Maps traffic between workloads and enforces segmentation across data centre, cloud and endpoints.",{"vendor_slug":126,"vendor_name":127,"vendor_logo":128,"product_slug":129,"product_name":130,"url":131,"description":132},"akamai","Akamai","\u002Fvendor-logos\u002Fakamai.png","guardicore-segmentation","Akamai Guardicore Segmentation","https:\u002F\u002Fwww.akamai.com\u002Fproducts\u002Fakamai-guardicore-segmentation","Software-based microsegmentation with traffic visibility.",[134,139],{"slug":135,"name":21,"logo_path":136,"roles":137},"beyondtrust","\u002Fvendor-logos\u002Fbeyondtrust.png",[138],"affected",{"slug":140,"name":141,"logo_path":142,"roles":143},"rapid7","Rapid7","\u002Fvendor-logos\u002Frapid7.png",[144],"reported_by",{"slug":146,"title":147,"body":148,"framework_refs":149,"framework_notes":157},"unauthenticated-file-upload-leads-to-rce-in-beyondtrust-remote-support","Unauthenticated File Upload Leads to RCE in BeyondTrust Remote Support","A critical origin-validation flaw in BeyondTrust Remote Support (CVE-2026-102764) allows unauthenticated attackers to upload malicious PHP files through the public-facing customer connection portal and achieve remote code execution with no credentials or user interaction required. This is particularly dangerous because BeyondTrust is a privileged access management tool, meaning a compromised instance can act as a launchpad into every system it manages. The vulnerability is reachable before any authentication gate, collapsing the assumption that internet-facing support portals are inherently low-risk entry points. Organizations that delay patching internet-exposed critical infrastructure dramatically expand their attack surface and risk full environment compromise through a single unpatched endpoint.",[150,151,152,153,154,155,156],"CIS Control 7: Continuous Vulnerability Management","NIST SP 800-53 SI-2: Flaw Remediation","OWASP A06:2021 Vulnerable and Outdated Components","ISO\u002FIEC 27001:2022 A.8.8: Management of technical vulnerabilities","NIST CSF 2.0 PR.AA: Identity Management, Authentication, and Access Control","NIS2 Article 21(2)(b)","PCI DSS Requirement 6",{"NIS2 Article 21(2)(b)":158,"PCI DSS Requirement 6":159,"NIST SP 800-53 SI-2: Flaw Remediation":160,"OWASP A06:2021 Vulnerable and Outdated Components":161,"CIS Control 7: Continuous Vulnerability Management":162,"ISO\u002FIEC 27001:2022 A.8.8: Management of technical vulnerabilities":163,"NIST CSF 2.0 PR.AA: Identity Management, Authentication, and Access Control":164},"NIS2 requires entities to implement incident handling and vulnerability management measures proportionate to risk; the availability of an unauthenticated RCE in a privileged-access tool is precisely the critical-risk scenario NIS2 mandates proactive controls and rapid patching to address.","Requirement 6 mandates that organizations develop and maintain secure systems by applying security patches promptly and protecting public-facing web applications; both the unpatched flaw and the lack of file-upload controls on the customer portal violate this requirement.","SI-2 requires organizations to identify, report, and correct information system flaws on defined timelines; a formal flaw remediation process would have mandated emergency patching of this critical RCE vulnerability in BeyondTrust before exploitation.","The BeyondTrust instance running unpatched software containing a known critical flaw is a direct instance of A06, where using components with known vulnerabilities in internet-facing applications enables unauthenticated code execution.","Regular, prioritized vulnerability scanning of internet-facing assets would have flagged CVE-2026-102764 immediately upon publication, triggering an accelerated remediation timeline before attackers could exploit the unauthenticated upload path.","A.8.8 mandates timely identification and remediation of technical vulnerabilities; had this control been applied with appropriate urgency for internet-exposed privileged-access infrastructure, the vulnerable version would not have remained in production.","The vulnerability bypasses authentication entirely on a public endpoint; enforcing access controls such as IP allowlisting or pre-authentication VPN for the customer portal would have limited exposure even before a patch was available."]