rConfig RCE via Redis Job Queue (CVE-2026-124079)
rConfig Core 8.0.0 through 8.2.18 and Pro before 8.4.1 let an attacker run code through the Redis job queue, fixed in 8.2.19 and 8.4.1.
TL;DR
- rConfig Core 8.0.0 through 8.2.18 and Pro before 8.4.1 deserialize jobs from a Redis-backed Horizon queue, so a crafted job can run code as the queue worker. VulnCheck published CVE-2026-124079 on 2026-11-09.
- A direct attacker needs Redis reachable without a password. A signed-in user can also point a device at the rConfig server's loopback address and write to the same queue, as the vendor advisory describes.
- VulnCheck rates it CVSS v3.1 9.8 Critical and CVSS v4.0 9.3 Critical. No exploitation is known, and it is not in CISA KEV as of 2026-11-09.
- Upgrade Core to 8.2.19 or later and Pro to 8.4.1 or later. Rotate stored secrets after the upgrade; "How do I fix it?" covers the interim controls.
Am I affected?
All of these must hold:
- You run rConfig Core 8.0.0 through 8.2.18, or rConfig Pro before 8.4.1.
- The deployment uses the Redis queue driver with Laravel Horizon, which is the default for these releases.
- Either Redis is reachable from an untrusted network without a password, or an attacker can sign in and add or edit a device.
The vendor lists Core 8.2.18 as affected even though 8.2.18 added an allow-list to the TaskDownloadRun job. The allow-list closed the direct Artisan argument injection in that job. The queue stayed unauthenticated, and a device could still point at the server's loopback address. Core 8.2.19 is the first release the vendor lists as fixed.
| Branch | Affected versions | Fixed version |
|---|---|---|
| Core 8.x | 8.0.0 through 8.2.18 | 8.2.19 (8.2.20 is current) |
| Pro | before 8.4.1 | 8.4.1 (8.4.2 is due shortly) |
Check the installed version:
cat /var/www/html/rconfig/VERSION
# Docker:
docker exec rconfig_app cat /var/www/html/rconfig/VERSION
Check whether Redis requires a password. Run this on the rConfig host:
grep -E '^REDIS_(HOST|PORT|PASSWORD)=' /var/www/html/rconfig/.env
redis-cli -h 127.0.0.1 -p 6379 ping
PONG means Redis accepted the request without a password. NOAUTH means a password is required. If the reply is PONG and port 6379 is reachable from an untrusted network, the direct path is open.
Find devices pointed at the server itself:
mysql -u rconfig_user -p rconfig -e "
SELECT id, device_name, device_ip, device_port_override
FROM devices
WHERE device_ip IN ('127.0.0.1','localhost','::1','0.0.0.0')
OR device_ip LIKE '127.%'
OR device_ip LIKE '::ffff:127.%';"
How do I fix it?
Upgrade Core to 8.2.19 or later. Version 8.2.20 is the current release and also fixes the companion table-export credential disclosure, rCFG-SA-2026-10. Pro users upgrade to 8.4.1 or later; the vendor says 8.4.2 is due shortly.
- For a Core install upgrading from 8.2.18 or earlier, run the vendor's first-upgrade sequence:
cd /var/www/html/rconfig
git pull
sudo ./update.sh --no-pull
- After that first upgrade, the single upgrade command is:
sudo ./update.sh
- For Docker, pin the image version in
.env, then pull and recreate the container:
sed -i 's/^RCONFIG_VERSION=.*/RCONFIG_VERSION=8.2.20/' .env
docker compose pull
docker compose up -d
The 8.2.19 and later images run Redis inside the application container and generate its password on first start.
- For Pro, follow the vendor's Pro upgrade instructions to 8.4.1 or later.
If you cannot upgrade now, apply the two controls you can reproduce on 8.2.18. First, put a password on Redis. The 8.2.19 command is php artisan rconfig:redis-secure, but that command does not exist on 8.2.18. On 8.2.18, apply the same control manually, adapted from the vendor's RedisSecurityService:
SECRET=$(openssl rand -hex 32)
redis-cli -h 127.0.0.1 -p 6379 CONFIG SET requirepass "$SECRET"
redis-cli -h 127.0.0.1 -p 6379 -a "$SECRET" --no-auth-warning CONFIG REWRITE
sed -i "s/^REDIS_PASSWORD=.*/REDIS_PASSWORD=$SECRET/" /var/www/html/rconfig/.env
cd /var/www/html/rconfig
php artisan config:clear
php artisan horizon:terminate
The horizon:terminate signal makes Supervisor relaunch Horizon with the new password. If you run the scheduler separately, restart it too.
Second, restrict device editing to trusted administrators until you upgrade. The 8.2.18 UI still accepts a device IP of 127.0.0.1 or localhost, so the loopback path stays open to any account that can add or edit a device. Keep Redis bound to loopback or a private network and firewall port 6379. That reduces the direct path, but it does not close the loopback path.
After upgrading, rotate the secrets the queue worker could read. The vendor advisory lists REST API tokens, device and enable passwords, and SMTP, LDAP and RADIUS secrets. Review the user list for accounts you do not recognize.
What is the root cause?
The queue is Redis-backed through Laravel Horizon, and Redis had no password by default. Laravel's CallQueuedHandler deserializes the job payload without a class allow-list, so a job that names a class with a dangerous handle() method runs that method. The bundled TaskDownloadRun job was one such class: before 8.2.18 it passed its serialized task_command and id fields straight to Artisan::call().
At the 8.2.18 tag, config/database.php lines 121 to 126 set the Redis password from REDIS_PASSWORD with a default of null:
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
The .env.example file ships REDIS_PASSWORD=null. A default install therefore connects to Redis without authentication.
At the 8.2.17 tag, app/Jobs/TaskDownloadRun.php line 32 passed the serialized task fields to Artisan:
Artisan::call($this->task['task_command'] . ' ' . $this->task['id']);
The job's properties travel inside the queue payload. An attacker who can write to the queue controls task_command and id, so the worker runs any Artisan command, including tinker. The 8.2.18 changelog records the fix in its own words. It says TaskDownloadRun passed the command and ID from its own serialized properties straight to Artisan::call(), so a crafted queue payload could run any Artisan command.
Version 8.2.18 added an allow-list in app/Jobs/TaskDownloadRun.php lines 58 to 78:
$command = is_string($this->task['task_command'] ?? null) ? $this->task['task_command'] : '';
if (! in_array($command, self::ALLOWED_TASK_COMMANDS, true)) {
Log::warning('TaskDownloadRun rejected an unexpected task command.', [
'task_command' => $command,
]);
return;
}
if (! is_numeric($this->task['id'] ?? null)) {
Log::warning('TaskDownloadRun rejected a non-numeric task id.', [
'id' => $this->task['id'] ?? null,
]);
return;
}
$taskId = (int) $this->task['id'];
Artisan::call(self::RUN_COMMAND, ['taskid' => [$taskId]]);
That allow-list closed the direct Artisan argument injection in this job. It did not close the queue itself, and it did not stop a device from pointing at the server. The vendor lists 8.2.18 as affected and fixed the remaining paths in 8.2.19.
The second path is the device target. At the 8.2.19 tag, app/Rules/DeviceIpIsValid.php lines 29 to 36 reject a device that points at the server:
if ($this->pointsAtThisServer($value)) {
$fail('The :attribute cannot point at this rConfig server (loopback, localhost or its Redis host).');
}
/**
* A device is something rConfig connects out to from its own server, so a device on
* loopback lets device editors make rConfig write into its own Redis. See RCO-1813.
*/
The rule is attached to device_ip on both device creation and update in app/Http/Requests/StoreDeviceRequest.php lines 56 and 71. The Telnet connection opens a raw TCP socket with fsockopen($this->hostname, $this->port), and Send::sendString() writes $command . "\r\n" to it. A device at 127.0.0.1 on port 6379 therefore turns the device download into a raw TCP writer to Redis.
The vendor's own fix code states the risk in app/Services/Utilities/RedisSecurityService.php. It says Core runs Horizon workers that deserialize queue payloads, so an unauthenticated Redis that any local process can write to is a code-execution path.
The impact was worse on native installs because the queue worker ran as root. At the 8.2.18 tag, horizon_supervisor.ini sets user=root:
[program:horizon]
process_name=%(program_name)s
command=php /var/www/html/rconfig/artisan horizon
autostart=true
autorestart=true
user=root
The 8.2.19 release changes that to user=RCONFIG_WEB_USER, so a future queue flaw no longer runs with full system rights.
How is it exploited?
- Reach the queue. If Redis is reachable without a password, the attacker connects to port 6379 directly.
- Use the device path if Redis is only on loopback. The attacker signs in and creates or edits a device with
device_ipset to127.0.0.1,localhost,::1or0.0.0.0, anddevice_port_overrideset to6379. - Write a job. The device connection opens a TCP socket to the target and writes the command string. Pointed at Redis, the same socket accepts the Redis command that pushes a serialized Laravel job onto the Horizon queue.
- The worker deserializes it. Horizon's
CallQueuedHandlerdeserializes the payload and calls the job'shandle()method. The payload can nameTaskDownloadRunor another class with a dangerous handler. - The job runs. On releases before 8.2.18,
TaskDownloadRunruns the attacker's Artisan command. On 8.2.18, that job's allow-list blocks the direct command, but the queue still deserializes other classes and the loopback path remains. The bundledlaravel/tinkerpackage turns an Artisan command into arbitrary PHP and operating system commands. - The attacker persists. The CVE description records a web shell in
storage/app/public, plus database credentials, stored device credentials and API tokens.
No public proof of concept is linked from the advisory or the CVE record as of 2026-11-09. These steps describe the mechanism the sources document; they are not a working exploit.
How does the attack flow?
An attacker who can reach the Redis queue, directly or through a loopback device, writes a serialized job that Horizon deserializes and runs as the queue worker.
no password check"] B --> C["Push serialized job
to Horizon queue"] C --> D["CallQueuedHandler
no class allow-list"] D --> E["TaskDownloadRun or tinker
runs Artisan command"] E --> F["Code execution as
queue worker"] F --> G["Web shell and stored
device credentials"]
What can an attacker do?
The attacker runs code as the queue worker. On native installs that used the 8.2.18 supervisor stub, that user was root. In the Docker image, the worker runs as www-data.
The impact conditions are:
- Direct Redis access requires the Redis port to be reachable from the attacker's network and to have no password.
- The loopback path requires a signed-in rConfig account that can create or edit a device.
- The queue worker must be running Horizon, which is the default for the affected releases.
With code execution, the CVE description records these capabilities:
- Write a persistent web shell into the web-served
storage/app/publicdirectory. - Read database credentials and API tokens.
- Read the stored device credentials for the network devices that rConfig manages.
No exploitation in the wild is known, and the CVE is not in CISA KEV as of 2026-11-09.
Why does the fix work?
The fix closes both paths to the queue and lowers the impact of any future queue flaw. The vendor's advisory lists Core 8.2.19 and Pro 8.4.1 as the fixed releases.
The first change rejects device targets that point at the server's loopback or Redis host. The 8.2.19 diff adds pointsAtThisServer() to the existing DeviceIpIsValid rule:
@@ -22,7 +22,48 @@
// Check if it's a valid IP address or hostname/FQDN
if (! $this->isValidIpAddress($value) && ! $this->isValidHostname($value)) {
$fail('The :attribute must be a valid IP address or FQDN/Hostname.');
+
+ return;
+ }
+
+ if ($this->pointsAtThisServer($value)) {
+ $fail('The :attribute cannot point at this rConfig server (loopback, localhost or its Redis host).');
+ }
+ }
+
+ /**
+ * A device is something rConfig connects out to from its own server, so a device on
+ * loopback lets device editors make rConfig write into its own Redis. See RCO-1813.
+ */
+ private function pointsAtThisServer(string $value): bool
+ {
+ $host = strtolower(rtrim($value, '.'));
+
+ if ($host === 'localhost' || str_ends_with($host, '.localhost')) {
+ return true;
+ }
+
+ if ($host === strtolower((string) config('database.redis.default.host'))) {
+ return true;
+ }
+
+ $packed = @inet_pton($host);
+
+ if ($packed === false) {
+ return false;
+ }
+
+ // IPv4-mapped IPv6 (::ffff:127.0.0.1) reaches the same IPv4 address.
+ if (strlen($packed) === 16 && str_starts_with($packed, str_repeat("\0", 10) . "\xff\xff")) {
+ $packed = substr($packed, 12);
+ }
+
+ if (strlen($packed) === 4) {
+ // 127.0.0.0/8 is loopback, and Linux routes a connect to 0.0.0.0 to the local host.
+ return $packed[0] === "\x7f" || $packed === "\0\0\0\0";
+ }
+
+ return $packed === inet_pton('::1') || $packed === inet_pton('::');
}
The rule now rejects localhost, the configured Redis host, every address in 127.0.0.0/8, 0.0.0.0, ::1, :: and IPv4-mapped IPv6 forms. The authenticated loopback path is closed.
The second change puts a password on Redis. The new rconfig:redis-secure command calls RedisSecurityService::secure(), which generates a 32-byte secret, writes REDIS_PASSWORD to .env, and requires it on the live server:
$this->makeClient($current)->executeRaw(['CONFIG', 'SET', 'requirepass', $secret]);
It then runs CONFIG REWRITE so the password survives a restart, and restarts the queue consumers. The installer and the Docker entrypoint generate the same password on fresh installs. The unauthenticated direct path is closed.
The third change runs Horizon and the scheduler as the web user. The 8.2.19 horizon_supervisor.ini replaces user=root with user=RCONFIG_WEB_USER, and the release notes say the services no longer run as root. If another queue flaw appears, the worker no longer has full system rights.
The vendor's advisory lists 8.2.19 and 8.4.1 as the fixed releases for this CVE.
How do I check if I was compromised?
No public indicators of compromise are published for CVE-2026-124079. These checks look for the two access paths and for the persistence the CVE description records.
Check whether Redis is open and what is queued:
redis-cli -h 127.0.0.1 -p 6379 ping
redis-cli -h 127.0.0.1 -p 6379 --scan --pattern '*horizon*'
redis-cli -h 127.0.0.1 -p 6379 --scan --pattern '*queues*'
If Redis requires a password, add -a "$REDIS_PASSWORD" --no-auth-warning to each command. The scan lists the queue keys. Inspect any list key it returns with lrange <key> 0 -1. An empty queue is normal. A serialized payload that names an unexpected class is a lead to investigate.
Check for devices pointed at the server:
mysql -u rconfig_user -p rconfig -e "
SELECT id, device_name, device_ip, device_port_override, created_at
FROM devices
WHERE device_ip IN ('127.0.0.1','localhost','::1','0.0.0.0')
OR device_ip LIKE '127.%'
OR device_ip LIKE '::ffff:127.%';"
Check for a PHP web shell in the public storage directory:
find /var/www/html/rconfig/storage/app/public -type f -name '*.php' -printf '%TY-%Tm-%Td %TH:%TM %p\n'
grep -rIl '<?php' /var/www/html/rconfig/storage/app/public
Legitimate rConfig uploads do not need a PHP file in storage/app/public. Treat any PHP file there as suspicious and compare it with a known-good backup.
Check the application and queue logs for rejected jobs or unexpected Artisan commands:
grep -i 'TaskDownloadRun rejected' /var/www/html/rconfig/storage/logs/laravel.log
grep -iE 'tinker|artisan' /var/www/html/rconfig/storage/logs/laravel.log /var/www/html/rconfig/storage/logs/horizon.log
A rejected job means someone sent an unexpected command. A hit may be a failed attempt rather than a compromise. No public indicators exist, so a failed attempt may leave no trace.
Timeline
- 2026-10-08: rConfig Core 8.2.18 released with an allow-list in
TaskDownloadRun, a partial fix for the Artisan argument injection. - 2026-10-27: rConfig Core 8.2.19 released with Redis authentication, non-root queue workers and the loopback device block.
- 2026-11-06: rConfig publishes advisory rCFG-SA-2026-09 for this CVE and releases Core 8.2.20, which also fixes rCFG-SA-2026-10, the table-export credential disclosure.
- 2026-11-09: VulnCheck publishes CVE-2026-124079, crediting komi Sergio Hognaglo (manasse).
The gap from the 8.2.18 partial fix to the CVE publication was 32 days.
FAQ
Is CVE-2026-124079 exploited in the wild?
No exploitation is known. Neither the vendor advisory nor the VulnCheck advisory reports attacks, and the CVE is not in CISA KEV as of 2026-11-09.
Does the flaw affect rConfig Pro?
Yes. rConfig Pro before 8.4.1 is affected, and the vendor fixed it in 8.4.1. The vendor says 8.4.2 is due shortly with further fixes.
Is Core 8.2.18 fixed?
No. Core 8.2.18 added an allow-list to the TaskDownloadRun job, but the vendor lists 8.2.18 as affected. Core 8.2.19 is the first release the vendor lists as fixed.
Is my instance exposed if Redis is only on localhost?
It can be. A signed-in user who can add or edit a device can point that device at 127.0.0.1 on port 6379, and rConfig connects out to device targets. If device editing is limited to trusted administrators and Redis has a password, the exposure is lower, but the upgrade is still the fix.
What secrets should I rotate?
Rotate the REST API tokens, the stored device and enable passwords, and the SMTP, LDAP and RADIUS secrets. The vendor advisory also asks you to review the user list for accounts you do not recognize.
Can a firewall replace the upgrade?
No. A firewall or a loopback-only Redis binding reduces the direct path, but it does not close the device loopback path. Apply the Redis password and restrict device editing until you upgrade.
Find exposed rConfig servers with Ostorlab
Ostorlab's attack surface discovery inventories internet-facing hosts, so you can find rConfig instances that should not be reachable and patch them first. Start at ostorlab.co/product/attacksurface_discovery.
References
- VulnCheck, "rConfig Core 8.0.0 before 8.2.19 and rConfig Pro before 8.4.1 RCE via Unauthenticated Redis Queue Poisoning", https://www.vulncheck.com/advisories/rconfig-core-before-and-rconfig-pro-before-rce-via-unauthenticated-redis-queue-poisoning (checked November 9, 2026).
- CVE Program, "CVE-2026-124079", https://www.cve.org/CVERecord?id=CVE-2026-124079 (checked November 9, 2026).
- rConfig, "Security Advisories & Vulnerability Disclosures", https://www.rconfig.com/security-advisories (checked November 9, 2026).
- rConfig, "rConfig V8 Core 8.2.19", https://github.com/rconfig/rconfig/releases/tag/core-8.2.19 (checked November 9, 2026).
- rConfig, "rConfig V8 Core 8.2.20", https://github.com/rconfig/rconfig/releases/tag/core-8.2.20 (checked November 9, 2026).
- rConfig, "CHANGELOG.md at core-8.2.19", https://github.com/rconfig/rconfig/blob/core-8.2.19/CHANGELOG.md (checked November 9, 2026).
- rConfig, "app/Jobs/TaskDownloadRun.php at core-8.2.17", https://github.com/rconfig/rconfig/blob/core-8.2.17/app/Jobs/TaskDownloadRun.php (checked November 9, 2026).
- rConfig, "app/Jobs/TaskDownloadRun.php at core-8.2.18", https://github.com/rconfig/rconfig/blob/core-8.2.18/app/Jobs/TaskDownloadRun.php (checked November 9, 2026).
- rConfig, "app/Rules/DeviceIpIsValid.php at core-8.2.19", https://github.com/rconfig/rconfig/blob/core-8.2.19/app/Rules/DeviceIpIsValid.php (checked November 9, 2026).
- rConfig, "app/Http/Requests/StoreDeviceRequest.php at core-8.2.19", https://github.com/rconfig/rconfig/blob/core-8.2.19/app/Http/Requests/StoreDeviceRequest.php (checked November 9, 2026).
- rConfig, "config/database.php at core-8.2.18", https://github.com/rconfig/rconfig/blob/core-8.2.18/config/database.php (checked November 9, 2026).
- rConfig, "app/Services/Utilities/RedisSecurityService.php at core-8.2.19", https://github.com/rconfig/rconfig/blob/core-8.2.19/app/Services/Utilities/RedisSecurityService.php (checked November 9, 2026).
- rConfig, "horizon_supervisor.ini at core-8.2.18", https://github.com/rconfig/rconfig/blob/core-8.2.18/horizon_supervisor.ini (checked November 9, 2026).
- rConfig, "horizon_supervisor.ini at core-8.2.19", https://github.com/rconfig/rconfig/blob/core-8.2.19/horizon_supervisor.ini (checked November 9, 2026).
- rConfig, "app/Http/Controllers/Connections/Telnet/Connect.php at core-8.2.18", https://github.com/rconfig/rconfig/blob/core-8.2.18/app/Http/Controllers/Connections/Telnet/Connect.php (checked November 9, 2026).
- rConfig, "app/Http/Controllers/Connections/Telnet/Send.php at core-8.2.18", https://github.com/rconfig/rconfig/blob/core-8.2.18/app/Http/Controllers/Connections/Telnet/Send.php (checked November 9, 2026).
- CISA, "Known Exploited Vulnerabilities Catalog", https://www.cisa.gov/known-exploited-vulnerabilities-catalog (checked November 9, 2026).
- rConfig, "rconfig/rconfig on GitHub", https://github.com/rconfig/rconfig (checked November 9, 2026).
- Docker Hub, "rconfig/rconfig", https://hub.docker.com/r/rconfig/rconfig (checked November 9, 2026).