Initial Docker packaging

This commit is contained in:
2026-09-29 13:06:06 +01:00
commit 83f9035d6b
9 changed files with 189 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
.git
.gitmodules
.env
README.md
compose.yaml
app/.git
app/.github
app/.agents
app/.codex
app/LICENSE
app/README.md
app/serve.py
**/__pycache__
**/*.pyc
+1
View File
@@ -0,0 +1 @@
.env
+3
View File
@@ -0,0 +1,3 @@
[submodule "app"]
path = app
url = https://github.com/ntfargo/Relapse-Exploit.git
+28
View File
@@ -0,0 +1,28 @@
ARG PYTHON_IMAGE=python:3.14-alpine
FROM ${PYTHON_IMAGE}
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PORT=8080
WORKDIR /srv
COPY payloads.sha256 /tmp/payloads.sha256
COPY app/index.html /srv/public/index.html
COPY app/src /srv/public/src
COPY app/offsets /srv/public/offsets
COPY app/payloads /srv/public/payloads
COPY docker/server.py /srv/server.py
RUN cd /srv/public && sha256sum -c /tmp/payloads.sha256 \
&& rm /tmp/payloads.sha256 \
&& chown -R 65532:65532 /srv
USER 65532:65532
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/', timeout=2).close()"]
CMD ["python", "/srv/server.py"]
+76
View File
@@ -0,0 +1,76 @@
# Relapse Exploit Docker
Container packaging for [PS5 Relapse Exploit](https://github.com/ntfargo/Relapse-Exploit).
The upstream project is pinned as a Git submodule, and the image build verifies the
SHA-256 digest of every executable payload before including it.
Use this only on consoles you own or are authorized to test. The exploit can panic
the console, corrupt kernel state, expose administrative services, and execute
arbitrary code with elevated privileges.
## Clone and build
```sh
git clone --recurse-submodules <repository-url>
cd relapse-exploit-docker
docker compose build
```
For an existing clone:
```sh
git submodule update --init --recursive
docker compose build
```
## Run
```sh
docker compose up -d
```
Open `http://HOST-IP:8000/` from the PS5. View the service status with:
```sh
docker compose ps
docker compose logs -f
```
Stop it with `docker compose down`.
By default, port 8000 is exposed on every host interface so the PS5 can reach it.
Restrict it to one host address when possible:
```sh
HOST_BIND=192.168.1.10 HOST_PORT=8000 docker compose up -d
```
Do not expose this service to the internet. Prefer an isolated local network.
## Update upstream
Review upstream changes before updating because the served JavaScript and payloads
ultimately execute with kernel privileges.
```sh
git -C app fetch origin
git -C app checkout <reviewed-commit>
sha256sum app/payloads/*
```
After reviewing the new binaries, update `payloads.sha256`, rebuild, test, and commit
both the submodule pointer and checksum changes together.
## Container hardening
The runtime image:
- contains only the files required by the exploit page;
- runs as numeric user/group `65532:65532`;
- has a read-only filesystem, all Linux capabilities dropped, and no-new-privileges;
- disables directory listings and browser caching;
- adds restrictive browser security headers;
- does not expose the upstream `.git` directory.
These controls protect the host-side web server. They do not make the PS5 exploit or
its precompiled payloads intrinsically safe.
Submodule
+1
Submodule app added at 09f10f5b58
+15
View File
@@ -0,0 +1,15 @@
services:
relapse-exploit:
build:
context: .
image: relapse-exploit:local
ports:
- "${HOST_BIND:-0.0.0.0}:${HOST_PORT:-8000}:8080"
read_only: true
tmpfs:
- /tmp:size=1m,mode=1777
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
restart: unless-stopped
+46
View File
@@ -0,0 +1,46 @@
import http.server
import mimetypes
import os
from pathlib import Path
PUBLIC_ROOT = Path("/srv/public")
PORT = int(os.environ.get("PORT", "8080"))
mimetypes.add_type("text/javascript", ".js")
mimetypes.add_type("application/octet-stream", ".elf")
mimetypes.add_type("application/octet-stream", ".bin")
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=PUBLIC_ROOT, **kwargs)
def list_directory(self, path):
self.send_error(404, "Not found")
return None
def end_headers(self):
self.send_header("Cache-Control", "no-store, max-age=0")
self.send_header("Pragma", "no-cache")
self.send_header("X-Content-Type-Options", "nosniff")
self.send_header("X-Frame-Options", "DENY")
self.send_header("Referrer-Policy", "no-referrer")
self.send_header(
"Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; "
"worker-src 'self'; child-src 'self'; connect-src 'self'; img-src 'self' data:; "
"object-src 'none'; base-uri 'none'; frame-ancestors 'none'",
)
super().end_headers()
class Server(http.server.ThreadingHTTPServer):
daemon_threads = True
allow_reuse_address = True
if __name__ == "__main__":
with Server(("0.0.0.0", PORT), Handler) as server:
print(f"Serving Relapse Exploit on port {PORT}", flush=True)
server.serve_forever()
+5
View File
@@ -0,0 +1,5 @@
de5dd480d12637527ba2d75e35de851adbb59c455293fa32588c492ef6b44b81 payloads/elfldr-ps5-1360.elf
26034dfbb88dd8be344fee1876c387bfcba0386edb079014e18ec25580368f6d payloads/etaHEN.elf
7cfb3a8cb86db67893c360ae3531f460f800e9f24039797038e6e81fc50f18a9 payloads/kexp_2026_05_25.bin
858880a8adbedaabfaced025384f4f6f908cfe44f1a049070ad8df911c22b972 payloads/kstuff.elf
0b30a1c23b83ebb2aa6523c8caaa5801ee89bde00e90cb36f392a29bb0bdb403 payloads/shadowmountplus.elf