Skip to content

Commit 8808bba

Browse files
committed
switch to direct ctfd communication for the dojo command
1 parent 0cd9a4e commit 8808bba

File tree

8 files changed

+38
-227
lines changed

8 files changed

+38
-227
lines changed

docker-compose.yml

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -159,24 +159,10 @@ services:
159159
condition: service_started
160160
cache:
161161
condition: service_started
162-
dojo-socket:
163-
condition: service_started
164-
165-
dojo-socket:
166-
container_name: dojo-socket
167-
profiles:
168-
- main
169-
hostname: dojo-socket
170-
build: ./dojo-socket
171-
restart: always
172-
volumes:
173-
- /var/run/docker.sock:/var/run/docker.sock:ro
174-
- /data/dojo-command:/var/run/dojo-command
175-
healthcheck:
176-
test: ["CMD", "test", "-S", "/var/run/dojo-command/socket"]
177-
interval: 10s
178-
timeout: 5s
179-
retries: 3
162+
networks:
163+
default:
164+
workspace_net:
165+
ipv4_address: 10.0.0.2
180166

181167
db:
182168
container_name: db

dojo-socket/Dockerfile

Lines changed: 0 additions & 16 deletions
This file was deleted.

dojo-socket/dojo-socket-service.py

Lines changed: 0 additions & 127 deletions
This file was deleted.

dojo/dojo-init

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ fi
9090
mkdir -p /data/workspace/nix
9191
mkdir -p /data/workspacefs/bin
9292
mkdir -p /data/ctfd-ipython
93-
mkdir -p /data/dojo-command
9493

9594
mkdir -p /data/homes
9695
if [ "$(findmnt -n -o FSTYPE /data/homes)" != "btrfs" ] && [ "$(findmnt -n -o FSTYPE /data)" != "btrfs" ]; then
@@ -148,6 +147,7 @@ iptables -I DOCKER-USER -i workspace_net -j DROP
148147
for host in $(cat /opt/pwn.college/user_firewall.allowed); do
149148
iptables -I DOCKER-USER -i workspace_net -d $(host $host | awk '{print $NF; exit}') -j ACCEPT
150149
done
150+
iptables -I DOCKER-USER -i workspace_net -s 10.0.0.0/8 -d 10.0.0.2 -m conntrack --ctstate NEW -j ACCEPT
151151
iptables -I DOCKER-USER -i workspace_net -s 10.0.0.0/24 -m conntrack --ctstate NEW -j ACCEPT
152152
iptables -I DOCKER-USER -i workspace_net -d 10.0.0.0/8 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
153153
iptables -I DOCKER-USER -i workspace_net -s 192.168.42.0/24 -m conntrack --ctstate NEW -j ACCEPT

dojo_plugin/api/v1/docker.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,6 @@ def start_container(docker_client, user, as_user, user_mounts, dojo_challenge, p
122122
read_only=True,
123123
propagation="slave",
124124
),
125-
docker.types.Mount(
126-
"/run/dojo/socket",
127-
f"{HOST_DATA_PATH}/dojo-command/socket",
128-
"bind",
129-
read_only=True,
130-
),
131125
*user_mounts,
132126
]
133127

@@ -172,6 +166,7 @@ def start_container(docker_client, user, as_user, user_mounts, dojo_challenge, p
172166
"challenge.localhost": "127.0.0.1",
173167
"hacker.localhost": "127.0.0.1",
174168
"dojo-user": user_ipv4(user),
169+
"ctfd": "10.0.0.2",
175170
**USER_FIREWALL_ALLOWED,
176171
},
177172
init=True,

dojo_plugin/api/v1/integrations.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616
)
1717

1818

19-
def authenticate_container(auth_code):
20-
if not auth_code:
21-
return None, ({"success": False, "error": "Missing auth_code"}, 400)
19+
def authenticate_container(auth_token):
20+
if not auth_token:
21+
return None, ({"success": False, "error": "Missing auth_token"}, 400)
2222

2323
docker_client = docker.DockerClient(base_url="unix://var/run/docker.sock")
2424

2525
container = None
2626
try:
2727
for c in docker_client.containers.list():
28-
if c.labels.get("dojo.auth_token") == auth_code:
28+
if c.labels.get("dojo.auth_token") == auth_token:
2929
container = c
3030
break
3131
except Exception as e:
@@ -52,13 +52,13 @@ def authenticate_container(auth_code):
5252
class IntegrationSolve(Resource):
5353
def post(self):
5454
data = request.get_json()
55-
auth_code = data.get("auth_code")
55+
auth_token = data.get("auth_token") or data.get("auth_code") # Support both for backward compatibility
5656
submission = data.get("submission")
5757

5858
if not submission:
5959
return {"success": False, "error": "Missing submission"}, 400
6060

61-
auth_result, error_response = authenticate_container(auth_code)
61+
auth_result, error_response = authenticate_container(auth_token)
6262
if error_response:
6363
return error_response
6464

test/test_dojo_command.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,26 @@ def test_integrations_solve_endpoint(example_dojo, random_user):
1515

1616
response = requests.post(
1717
f"{DOJO_URL}/pwncollege_api/v1/integrations/solve",
18-
json={"auth_code": auth_token, "submission": flag}
18+
json={"auth_token": auth_token, "submission": flag}
1919
)
2020
assert response.status_code == 200
2121
assert response.json()["status"] == "solved"
2222

2323
response = requests.post(
2424
f"{DOJO_URL}/pwncollege_api/v1/integrations/solve",
25-
json={"auth_code": auth_token, "submission": flag}
25+
json={"auth_token": auth_token, "submission": flag}
2626
)
2727
assert response.status_code == 200
2828
assert response.json()["status"] == "already_solved"
2929

3030

3131
def test_dojo_command_files(example_dojo, random_user):
32-
"""Test that socket has correct permissions"""
3332
uid, session = random_user
3433
start_challenge(example_dojo, "hello", "apple", session=session)
3534

3635
result = workspace_run("ls -la /run/dojo/bin/dojo", user=uid)
3736
assert result.returncode == 0, f"dojo command should exist, got {result.stderr}"
3837

39-
result = workspace_run("ls -la /run/dojo/socket", user=uid)
40-
assert result.returncode == 0, f"Socket should exist, got {result.stderr}"
41-
assert "srw" in result.stdout, f"Expected socket file, got {result.stdout}"
42-
4338

4439
def test_dojo_command_submit(random_user, example_dojo):
4540
uid, session = random_user

workspace/core/dojo-command.nix

Lines changed: 24 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,18 @@
11
{ pkgs }:
22

3-
# This creates the /run/dojo/bin/dojo command available in user containers
4-
# The command communicates with the socket service running in the CTFd container
53
pkgs.writeScriptBin "dojo" ''
64
#!${pkgs.python3}/bin/python3
75
8-
import socket
96
import json
107
import sys
118
import os
12-
import struct
139
import argparse
10+
import urllib.request
11+
import urllib.error
1412
15-
SOCKET_PATH = "/run/dojo/socket"
16-
17-
def send_message(sock, data):
18-
sock.sendall(struct.pack("!I", len(data)) + data)
19-
20-
def recv_message(sock):
21-
length_data = sock.recv(4)
22-
if len(length_data) != 4:
23-
return None
24-
length = struct.unpack("!I", length_data)[0]
25-
26-
data = b""
27-
while len(data) < length:
28-
chunk = sock.recv(min(length - len(data), 4096))
29-
if not chunk:
30-
return None
31-
data += chunk
32-
return data
13+
CTFD_URL = "http://ctfd:8000"
3314
3415
def submit_flag(flag):
35-
if not os.path.exists(SOCKET_PATH):
36-
print("Error: Dojo socket not available. Are you running this inside a challenge container?")
37-
return 1
38-
3916
if int(open("/run/dojo/sys/workspace/privileged").read()):
4017
print("Error: workspace is in practice mode. Flag submission disabled.")
4118
return 1
@@ -46,39 +23,40 @@ pkgs.writeScriptBin "dojo" ''
4623
return 1
4724
4825
try:
49-
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
50-
sock.connect(SOCKET_PATH)
51-
52-
request = {
53-
"command": "submit_flag",
26+
request_data = {
5427
"auth_token": auth_token,
55-
"flag": flag
28+
"submission": flag
5629
}
5730
58-
send_message(sock, json.dumps(request).encode())
59-
response_data = recv_message(sock)
31+
req = urllib.request.Request(
32+
f"{CTFD_URL}/pwncollege_api/v1/integrations/solve",
33+
data=json.dumps(request_data).encode(),
34+
headers={"Content-Type": "application/json"}
35+
)
6036
61-
if not response_data:
62-
print("Error: No response from server")
63-
return 1
64-
65-
response = json.loads(response_data.decode())
37+
response = urllib.request.urlopen(req, timeout=10)
38+
result = json.loads(response.read().decode())
6639
67-
if response.get("success"):
68-
print(f"✓ {response.get('message', 'Flag submitted successfully!')}")
40+
if response.status == 200:
41+
if result.get("status") == "already_solved":
42+
print("✓ You already solved this challenge!")
43+
else:
44+
print("✓ Congratulations! Flag accepted!")
6945
return 0
7046
else:
71-
print(f"✗ {response.get('message', 'Unknown error')}")
47+
print(f"✗ {result.get('message', result.get('status', 'Flag submission failed'))}")
7248
return 1
7349
74-
except socket.error as e:
75-
print(f"Error: Unable to connect to dojo service: {e}")
50+
except urllib.error.HTTPError as e:
51+
result = json.loads(e.read().decode())
52+
print(f"✗ {result.get('message', result.get('status', 'Flag submission failed'))}")
53+
return 1
54+
except urllib.error.URLError as e:
55+
print(f"Error: Unable to connect to CTFd service: {e}")
7656
return 1
7757
except Exception as e:
7858
print(f"Error: {e}")
7959
return 1
80-
finally:
81-
sock.close()
8260
8361
def main():
8462
parser = argparse.ArgumentParser(

0 commit comments

Comments
 (0)