
Step-by-step exploit writeup for CVE-2017-11610 (Supervisord XML-RPC RCE) with attack surface analysis, namespace traversal discovery, and post-exploitation techniques in a Docker lab environment.
Starting with what is running in the environment. I list all active containers:
docker ps-a

The victim exposes a single port: 9001.
Port 9001 is not a standard web app. Looking up the port database shows this port could be associated with Supervisord (ETL Service Manager according to IANA), Tor proxy, or some other internal service. However, we cannot draw a conclusion based solely on the port number.
⇒ I curl directly to read the response and also access the web GUI to gather more information
curl -i http://192.168.3.137:9001/


Response analysis:
Server: Medusa/1.12 and the title Supervisor Status→ Confirmed this is Supervisord, not Tor or any other service.
REFRESH, RESTART ALL, STOP ALLSupervisord is a process manager on Linux. If port 9001 is exposed to the network without a password, this is a dangerous configuration. An attacker could view services, restart/stop processes, and under certain configurations, exploit it to execute commands if they have privileges to modify or control the managed programs.
Analysis Conclusion: We can confirm the target is exposing the Supervisord administration interface to the network at port 9001. This is not a standard web service, but a management interface used to monitor and control processes. The ability to access this interface without authentication creates a risk that an attacker can view the status of or interact with the managed services.
However, we must distinguish between the visible UI and the underlying control mechanism. Buttons like REFRESH, RESTART ALL, and STOP ALL do not process requests independently on the frontend; instead, they must call an interface/backend of Supervisord to retrieve status or send process control commands. Therefore, after confirming the Web UI is exposed, the next analysis step is to determine if the underlying control interface exists behind the Web UI and if it requires authentication.
⇒ Thinking: Need to check whether the control interface behind the Web UI exists and if it requires authentication.

According to Supervisor's documentation, [inet_http_server] is an HTTP server listening on a TCP socket. This interface is not enabled by default, should only be used in trusted environments, does not support encryption, and has no default authentication unless username/password is configured.
The documentation also indicates that the port of [inet_http_server] is used to receive HTTP/XML-RPC requests; supervisorctl uses XML-RPC to communicate with supervisord via this port. This matches our observation in the lab: the container exposes 0.0.0.0:9001->9001/tcp, the Web UI is accessible without authentication, and the displayed version is Supervisor 3.3.2.
Thus, after confirming the Web UI on port 9001, the next step is to inspect the XML-RPC endpoint /RPC2. Based on the official mechanism of Supervisord, we need to verify these objectives:
/RPC2 exists.supervisor.getState or system.listMethods.Check if the endpoint is alive:
curl -i -s -X POST -H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><methodCall><methodName>supervisor.getState</methodName><params></params></methodCall>' \
http://192.168.3.137:9001/RPC2

The result returns HTTP/1.1 200 OK, not 401 Unauthorized or 403 Forbidden, showing the request was accepted by the server without credentials. The response is in the XML-RPC <methodResponse> format and contains statename=RUNNING and statecode=1, proving the /RPC2 endpoint is active and the supervisor.getState method was executed successfully.
⇒ Thinking: The attack surface is no longer limited to the Web UI, but has expanded to the XML-RPC API, where daemon/process control commands are handled. From here, the next analysis direction is to check how Supervisor handles methodName in XML-RPC, to determine if the current target exhibits the behavior of CVE-2017-11610, which lies in the dispatch/lookup mechanism of this method. We need to verify this to conclude if it is CVE-2017-11610.
In the previous step, we successfully called the supervisor.getState method via the /RPC2 endpoint. This raises the next question: when receiving a string-based methodName, how does Supervisord map that string to the internal Python function?
In XML-RPC, methods typically utilize namespaces, for example:
supervisor.getStatesupervisor.stopProcesssupervisor.getAllProcessInfoLogically, the server receives the methodName string, splits it by the dot ., and searches for the corresponding object/function inside the registered handler.
The pseudo-code can be understood as follows:
# Pseudo-code simulating the dispatch method concept in XML-RPC
def dispatch(method_name, params):
parts = method_name.split(".") # ["supervisor", "getState"]
obj = registered_handlers[parts[0]] # get namespace "supervisor"
for attr in parts[1:]:
obj = getattr(obj, attr) # lookup the next attribute
return obj(*params) # call the final function
For standard methods like supervisor.getState, this mechanism functions normally: the server retrieves the supervisor handler, then calls the getState function. However, the core issue of CVE-2017-11610 is that this lookup mechanism does not sufficiently restrict the attributes allowed to be accessed. If an attacker controls the methodName, they can not only call public methods like getState, but also traverse deeper into the internal objects/modules reachable from the supervisor handler.
In other words, the dot . in methodName is not just used to invoke valid methods, but can be abused to traverse object attributes.
This establishes our exploitation path:
supervisor → supervisord → options → warnings → linecache → os → system
The concept is to start from the supervisor handler, follow attributes to the daemon's internal objects, and then leverage pre-imported Python modules to reach os.system. If os.system can be called, the attacker can execute system commands with the privileges of the supervisord process.
Thus, the attack chain follows this logic:
/RPC2 accepts unauthenticated method calls → inspect how XML-RPC dispatches methodName → discover methodName can traverse object attributes → leads to calling os.system.