NVD has published CVE-2026-103395, a CVSS 9.8 (CRITICAL) network-exploitable vulnerability in LightLLM through version 1.2.0. When LightLLM is deployed in visual_only mode — a configuration used to offload multimodal/image inference workloads — it exposes an unauthenticated RPyC (Remote Python Call) service with allow_pickle enabled. The remote_infer_images method deserializes attacker-supplied arguments without authentication, and because pickle deserialization is enabled, an attacker can pass objects with crafted __reduce__ methods to achieve arbitrary code execution with the privileges of the LightLLM service account.
This is the classic Python pickle deserialization anti-pattern, weaponized against AI infrastructure — and it lands at a moment when LLM inference servers are being deployed rapidly, often with GPU nodes sitting inside trusted network segments with broad access to model weights, training data, and internal services. If you run LightLLM in any capacity, treat this as an emergency patch cycle item: the attack requires no authentication, no user interaction, and is trivially reachable over the network.
Technical Analysis
Affected Products and Versions
| Attribute | Detail |
|---|---|
| Product | LightLLM (high-performance LLM inference and serving framework) |
| Affected versions | Through 1.2.0 (all versions ≤ 1.2.0 in visual_only deployments) |
| CVE | CVE-2026-103395 |
| CVSS v3.1 | 9.8 (CRITICAL) — Network vector |
| Vulnerability class | CWE-502 — Deserialization of Untrusted Data |
| Attack vector | Unauthenticated network access to the visual RPyC service port |
Root Cause: How the Attack Works
LightLLM's visual server component is implemented as an RPyC service. RPyC is a Python RPC library that, when configured with allow_pickle=True, will serialize and deserialize arbitrary Python objects across the wire using Python's pickle module. This is a well-understood catastrophic design decision: pickle is not a serialization format — it is an execution engine. Any object deserialized by pickle can define a __reduce__ method that returns a callable and arguments, which Python will invoke during deserialization. Attackers classically use this to call os.system, subprocess.Popen, or posix_spawn before any application-level validation ever runs.
The exploitation chain for CVE-2026-103395 is:
- Reconnaissance: The attacker scans for or already knows the LightLLM visual RPyC service port on the target inference node.
- Connection: The attacker connects to the RPyC service. No authentication is required — the service accepts connections from any network peer that can reach the port.
- Invocation: The attacker calls the exposed
remote_infer_imagesmethod, passing a maliciously crafted object graph instead of legitimate image inference arguments. - Deserialization: RPyC unpickles the attacker-supplied arguments server-side. The attacker's
__reduce__payload executes arbitrary commands as the LightLLM service account — typically a privileged account with access to GPU resources, model files, API keys (often including upstream provider credentials), and internal network segments.
From a defender's perspective, the critical observables are: inbound network connections to the RPyC service from unexpected sources, and — the highest-fidelity signal — the LightLLM Python process spawning child processes that are not part of normal inference workloads (shells, interpreters, download cradles, reverse-connect tooling).
Exploitation Status
As of publication, CVE-2026-103395 is newly disclosed via NVD. The exploitation primitive (pickle __reduce__ RCE over unauthenticated RPyC) is mature, publicly documented, and requires no specialized exploit development — weaponization is measured in hours, not weeks, once adversaries map exposed instances. There is no indication yet of confirmed in-the-wild exploitation or CISA KEV inclusion, but defenders should assume internet-scanning for exposed RPyC ports is already underway. Shodan-class reconnaissance against AI infrastructure has been consistently aggressive throughout 2025–2026, and LLM serving stacks are high-value targets for cryptomining, model theft, and lateral movement staging.
Detection & Response
This is a technical threat — a remotely exploitable unauthenticated RCE. Full detection content follows.
Sigma Rules
The two rules below target the highest-fidelity behaviors: (1) the LightLLM Python process spawning unexpected child processes (post-exploitation execution), and (2) network connections to the RPyC visual service port from non-inference peers. Tune the port selection to your actual LightLLM deployment — the visual RPyC port is configurable, so enumerate it from your LightLLM startup configuration rather than assuming a default.
---
title: LightLLM Process Spawning Suspicious Child Process
description: Detects the LightLLM Python inference process spawning shells, interpreters, or download tooling — consistent with post-exploitation activity following CVE-2026-103395 pickle deserialization RCE via the visual RPyC service.
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-103395
- https://attack.mitre.org/techniques/T1059/006/
author: Security Arsenal
id: 3f7a9c21-4b8e-4d5a-9f12-8c6e2a1b7d44
status: experimental
date: 2026/01/15
tags:
- attack.execution
- attack.t1059.006
- attack.initial_access
- attack.t1190
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|contains:
- 'python'
ParentCommandLine|contains:
- 'lightllm'
selection_child:
Image|endswith:
- '/sh'
- '/bash'
- '/dash'
- '/zsh'
- '/curl'
- '/wget'
- '/nc'
- '/ncat'
- '/socat'
- '/python'
- '/perl'
- '/base64'
condition: selection_parent and selection_child
falsepositives:
- LightLLM maintenance scripts or health-check wrappers legitimately invoked by the service
level: high
---
title: Inbound Connection to LightLLM Visual RPyC Service Port
id: 9c2e5d47-1a6b-4f38-b7e4-5d3c8a2f6e91
status: experimental
description: Detects network connections to the LightLLM visual RPyC service port from sources outside expected inference orchestration peers — potential exploitation of CVE-2026-103395 unauthenticated RPyC exposure. Tune DestinationPort to the actual RPyC port configured in your LightLLM deployment.
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-103395
- https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/01/15
tags:
- attack.initial_access
- attack.t1190
- attack.exploitation_for_client_execution
logsource:
category: network_connection
product: linux
detection:
selection:
Image|contains: 'python'
DestinationPort:
- 18812
- 18813
filter_orchestrators:
SourceIp|startswith:
- '10.0.1.'
- '192.168.50.'
condition: selection and not filter_orchestrators
falsepositives:
- Internal inference orchestrators and API gateways that legitimately reach the visual server — replace the filter subnets with your actual peer ranges
level: medium
KQL Hunt — Microsoft Sentinel / Defender
The query below hunts two surfaces: Syslog-ingested process execution showing LightLLM Python processes spawning suspicious children, and network session data (via Defender for Endpoint or CEF-ingested firewall logs) hitting the RPyC visual service port from unexpected sources. Adjust the port list and the expected source prefixes to your environment.
// Hunt 1: LightLLM python process spawning shells/downloaders (post-exploitation execution)
let SuspiciousChildren = dynamic(["/bin/sh", "/bin/bash", "/bin/dash", "/usr/bin/curl", "/usr/bin/wget", "/bin/nc", "/usr/bin/ncat", "/usr/bin/socat", "/usr/bin/perl", "/usr/bin/base64"]);
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessCommandLine has "lightllm"
or InitiatingProcessFileName =~ "python"
and InitiatingProcessCommandLine has_any ("visual", "lightllm")
| where FileName in~ ("sh", "bash", "dash", "curl", "wget", "nc", "ncat", "socat", "perl", "base64")
or ProcessCommandLine has_any ("base64 -d", "/dev/tcp/", "socket.socket", "subprocess", "os.system", "curl ", "wget ")
| project TimeGenerated, DeviceName, AccountName, InitiatingProcessCommandLine, FileName, ProcessCommandLine, RemoteIP, RemotePort
| order by TimeGenerated desc;
// Hunt 2: Inbound connections to LightLLM visual RPyC port from non-orchestrator sources (CEF/Syslog firewall or Defender network data)
let RPyCPorts = dynamic([18812, 18813]); // Tune to your actual LightLLM visual RPyC port
let ExpectedPeers = dynamic(["10.0.1.", "192.168.50."]); // Tune to your inference orchestration ranges
CommonSecurityLog
| where TimeGenerated > ago(7d)
| where DestinationPort in (RPyCPorts)
| where not(SourceIP startswith_any (ExpectedPeers))
| summarize ConnectionCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
by SourceIP, DestinationIP, DestinationPort, DeviceAction
| order by ConnectionCount desc;
Velociraptor VQL Hunt
This artifact enumerates Python processes running LightLLM alongside their listening network sockets, so hunters can identify hosts where the RPyC visual service is exposed and bound to a non-localhost interface — the precondition for CVE-2026-103395 exploitation.
-- Identify LightLLM processes with exposed RPyC listening sockets (CVE-2026-103395 exposure surface)
LET procs = SELECT Pid, Name, CommandLine, Username
FROM pslist()
WHERE CommandLine =~ '(?i)lightllm'
LET listeners = SELECT Pid, Name, Laddr, Lport, Status
FROM netstat()
WHERE Status =~ 'LISTEN' AND Laddr !~ '^(127\.|::1|\[::1\])'
SELECT p.Pid AS Pid,
p.CommandLine AS LightLLMCommandLine,
p.Username AS ServiceAccount,
n.Laddr AS BoundAddress,
n.Lport AS ListenPort,
n.Status AS SocketStatus
FROM procs p
JOIN listeners n ON p.Pid = n.Pid
Remediation / Hardening Script
The following Bash script audits a Linux inference host for the CVE-2026-103395 exposure surface: it checks the installed LightLLM version, identifies whether the visual RPyC service is listening on a non-localhost interface, and applies an emergency host-level firewall block as a compensating control while you patch. Run as root or via sudo.
#!/bin/bash
# CVE-2026-103395 - LightLLM RPyC exposure audit and emergency mitigation
set -euo pipefail
echo "=== [1/4] Checking installed LightLLM version ==="
if command -v pip >/dev/null 2>&1; then
pip show lightllm 2>/dev/null | grep -i version || echo "LightLLM not found in default pip env - check virtualenvs/conda envs"
fi
# Also check common virtualenv locations
for env in /opt/*/venv /srv/*/venv /home/*/.venv /usr/local/lightllm*; do
[ -x "$env/bin/pip" ] && "$env/bin/pip" show lightllm 2>/dev/null | grep -i "name\|version"
done
echo "=== [2/4] Identifying LightLLM processes and exposed RPyC listeners ==="
LIGHTLLM_PIDS=$(pgrep -f lightllm || true)
if [ -n "$LIGHTLLM_PIDS" ]; then
for pid in $LIGHTLLM_PIDS; do
echo "--- PID $pid: $(tr '\0' ' ' < /proc/$pid/cmdline)"
ss -tlnp 2>/dev/null | grep "pid=$pid" | awk '{print " LISTENING:", $4}'
done
else
echo "No LightLLM processes found."
fi
echo "=== [3/4] Detecting RPyC port from LightLLM startup args ==="
# LightLLM visual RPyC port is set via startup config; search process cmdline and configs
RPYC_PORT=$(tr '\0' '\n' < /proc/$(pgrep -f lightllm | head -1)/cmdline 2>/dev/null | grep -A1 -i 'rpyc\|visual.*port' | grep -E '^[0-9]+$' | head -1 || true)
RPYC_PORT=${RPYC_PORT:-18812}
echo "RPyC visual port identified/assumed: $RPYC_PORT (verify against your LightLLM config)"
echo "=== [4/4] Emergency mitigation: restrict RPyC port to localhost/allowed peers ==="
read -rp "Apply iptables DROP for external traffic to port $RPYC_PORT? [y/N] " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
# Allow localhost and loopback, drop everything else to the RPyC port
iptables -A INPUT -i lo -p tcp --dport "$RPYC_PORT" -j ACCEPT
# Uncomment and adjust to whitelist your inference orchestrator subnet:
# iptables -A INPUT -s 10.0.1.0/24 -p tcp --dport "$RPYC_PORT" -j ACCEPT
iptables -A INPUT -p tcp --dport "$RPYC_PORT" -j DROP
echo "Firewall rules applied. Persist with: iptables-save > /etc/iptables/rules.v4"
else
echo "Skipped firewall changes."
fi
echo ""
echo "=== ACTION REQUIRED ==="
echo "1. Upgrade LightLLM to a version beyond 1.2.0 that addresses CVE-2026-103395."
echo "2. If visual_only mode is not required, disable the visual server entirely."
echo "3. Never expose the RPyC service port beyond trusted orchestration peers."
echo "4. Review logs for unexpected inbound connections to port $RPYC_PORT."
Remediation
1. Patch Immediately
- Upgrade LightLLM to a fixed release beyond 1.2.0. Monitor the LightLLM GitHub repository and the NVD entry for CVE-2026-103395 for the vendor-patched version and advisory details. Treat this as an emergency change — the CVSS 9.8 score reflects an unauthenticated, network-reachable RCE with a trivial exploitation primitive.
2. Eliminate the Exposure Surface
- Disable
visual_onlydeployments where multimodal inference is not a hard requirement. If the visual server isn't running, the vulnerable RPyC service doesn't exist. - Bind the RPyC service to loopback only (
127.0.0.1) where the visual server must run. There is no legitimate architectural reason for an internal RPC service to be reachable from arbitrary network peers. - If you maintain a fork or wrapper: set
allow_pickle=Falsein the RPyC service configuration and switch to a safe serializer (RPyC's brine, or explicit JSON/msgpack marshalling with schema validation). Pickle over a network socket should be treated as RCE-by-design.
3. Compensating Controls
- Network segmentation: Place GPU inference nodes in a dedicated segment with explicit allowlists. Only your inference orchestrator/API gateway should be able to reach the RPyC port — nothing else in the environment, and certainly nothing routable from the internet or user VLANs.
- Egress filtering: LightLLM service hosts should have no outbound internet access except to explicitly required model registries. A successful pickle RCE is far less useful to an attacker who cannot establish a reverse shell or pull second-stage tooling.
- Service account hardening: Run LightLLM under a dedicated low-privilege account with no sudo, no access to secrets stores beyond scoped model-serving credentials, and containerized isolation (with seccomp/AppArmor profiles) where possible.
4. Validate Exposure Posture
- Scan your own estate for listening RPyC ports before attackers do. An authenticated internal scan plus the VQL hunt above will surface exposed instances quickly.
- Review cloud security groups for GPU instances — a common failure mode is a broad
0.0.0.0/0ingress rule applied during a proof-of-concept that silently carried into production.
5. Assume Breach Where Exposure Existed
If your LightLLM visual RPyC port was reachable from untrusted networks for any period, treat the host as potentially compromised: review process execution history, check for unexpected outbound connections from the service account, rotate any credentials accessible to the LightLLM service (API keys, cloud instance roles, model registry tokens), and consider forensic imaging before redeployment.
Related Resources
Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.