Initial Docker packaging
This commit is contained in:
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "app"]
|
||||||
|
path = app
|
||||||
|
url = https://github.com/ntfargo/Relapse-Exploit.git
|
||||||
+28
@@ -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"]
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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()
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user